const TODO_EXCEPTION: jsc.C.ExceptionRef = null;

const log = bun.Output.scoped(.napi, false);

/// This is `struct napi_env__` from napi.h
pub const NapiEnv = opaque {
    pub fn toJS(self: *NapiEnv) *jsc.JSGlobalObject {
        return NapiEnv__globalObject(self);
    }

    extern fn napi_set_last_error(env: napi_env, status: NapiStatus) napi_status;

    /// Convert err to an extern napi_status, and store the error code in env so that it can be
    /// accessed by napi_get_last_error_info
    pub fn setLastError(self: ?*NapiEnv, err: NapiStatus) napi_status {
        return napi_set_last_error(self, err);
    }

    /// Convenience wrapper for setLastError(.ok)
    pub fn ok(self: *NapiEnv) napi_status {
        return self.setLastError(.ok);
    }

    /// These wrappers exist for convenience and so we can set a breakpoint in lldb
    pub fn invalidArg(self: *NapiEnv) napi_status {
        if (comptime bun.Environment.allow_assert) {
            log("invalid arg", .{});
        }
        return self.setLastError(.invalid_arg);
    }

    pub fn genericFailure(self: *NapiEnv) napi_status {
        if (comptime bun.Environment.allow_assert) {
            log("generic failure", .{});
        }
        return self.setLastError(.generic_failure);
    }

    /// Assert that we're not currently performing garbage collection
    pub fn checkGC(self: *NapiEnv) void {
        napi_internal_check_gc(self);
    }

    /// Return the Node-API version number declared by the module we are running code from
    pub fn getVersion(self: *NapiEnv) u32 {
        return napi_internal_get_version(self);
    }

    extern fn NapiEnv__globalObject(*NapiEnv) *jsc.JSGlobalObject;
    extern fn napi_internal_get_version(*NapiEnv) u32;
};

fn envIsNull() napi_status {
    // in this case we don't actually have an environment to set the last error on, so it doesn't
    // make sense to call napi_set_last_error
    @branchHint(.cold);
    return @intFromEnum(NapiStatus.invalid_arg);
}

/// This is nullable because native modules may pass null pointers for the NAPI environment, which
/// is an error that our NAPI functions need to handle (by returning napi_invalid_arg). To specify
/// a Zig API that uses a never-null napi_env, use `*NapiEnv`.
pub const napi_env = ?*NapiEnv;

/// Contents are not used by any Zig code
pub const Ref = opaque {};

pub const napi_ref = *Ref;

pub const NapiHandleScope = opaque {
    pub extern fn NapiHandleScope__open(env: *NapiEnv, escapable: bool) ?*NapiHandleScope;
    pub extern fn NapiHandleScope__close(env: *NapiEnv, current: ?*NapiHandleScope) void;
    extern fn NapiHandleScope__append(env: *NapiEnv, value: jsc.JSValue.backing_int) void;
    extern fn NapiHandleScope__escape(handleScope: *NapiHandleScope, value: jsc.JSValue.backing_int) bool;

    /// Create a new handle scope in the given environment, or return null if creating one now is
    /// unsafe (i.e. inside a finalizer)
    pub fn open(env: *NapiEnv, escapable: bool) ?*NapiHandleScope {
        return NapiHandleScope__open(env, escapable);
    }

    /// Closes the given handle scope, releasing all values inside it, if it is safe to do so.
    /// Asserts that self is the current handle scope in env.
    pub fn close(self: ?*NapiHandleScope, env: *NapiEnv) void {
        NapiHandleScope__close(env, self);
    }

    /// Place a value in the handle scope. Must be done while returning any JS value into NAPI
    /// callbacks, as the value must remain alive as long as the handle scope is active, even if the
    /// native module doesn't keep it visible on the stack.
    pub fn append(env: *NapiEnv, value: jsc.JSValue) void {
        NapiHandleScope__append(env, @intFromEnum(value));
    }

    /// Move a value from the current handle scope (which must be escapable) to the reserved escape
    /// slot in the parent handle scope, allowing that value to outlive the current handle scope.
    /// Returns an error if escape() has already been called on this handle scope.
    pub fn escape(self: *NapiHandleScope, value: jsc.JSValue) error{EscapeCalledTwice}!void {
        if (!NapiHandleScope__escape(self, @intFromEnum(value))) {
            return error.EscapeCalledTwice;
        }
    }
};

pub const napi_handle_scope = ?*NapiHandleScope;
pub const napi_escapable_handle_scope = ?*NapiHandleScope;
pub const napi_callback_info = *jsc.CallFrame;
pub const napi_deferred = *jsc.JSPromise.Strong;

/// To ensure napi_values are not collected prematurely after being returned into a native module,
/// you must use these functions rather than convert between napi_value and jsc.JSValue directly
pub const napi_value = enum(i64) {
    _,

    pub fn set(
        self: *napi_value,
        env: *NapiEnv,
        val: jsc.JSValue,
    ) void {
        NapiHandleScope.append(env, val);
        self.* = @enumFromInt(@intFromEnum(val));
    }

    pub fn get(self: *const napi_value) jsc.JSValue {
        return @enumFromInt(@intFromEnum(self.*));
    }

    pub fn create(env: *NapiEnv, val: jsc.JSValue) napi_value {
        NapiHandleScope.append(env, val);
        return @enumFromInt(@intFromEnum(val));
    }
};

const char16_t = u16;
pub const napi_property_attributes = c_uint;
pub const napi_valuetype = enum(c_uint) {
    undefined = 0,
    null = 1,
    boolean = 2,
    number = 3,
    string = 4,
    symbol = 5,
    object = 6,
    function = 7,
    external = 8,
    bigint = 9,
};
pub const napi_typedarray_type = enum(c_uint) {
    int8_array = 0,
    uint8_array = 1,
    uint8_clamped_array = 2,
    int16_array = 3,
    uint16_array = 4,
    int32_array = 5,
    uint32_array = 6,
    float32_array = 7,
    float64_array = 8,
    bigint64_array = 9,
    biguint64_array = 10,

    pub fn fromJSType(this: jsc.JSValue.JSType) ?napi_typedarray_type {
        return switch (this) {
            .Int8Array => napi_typedarray_type.int8_array,
            .Uint8Array => napi_typedarray_type.uint8_array,
            .Uint8ClampedArray => napi_typedarray_type.uint8_clamped_array,
            .Int16Array => napi_typedarray_type.int16_array,
            .Uint16Array => napi_typedarray_type.uint16_array,
            .Int32Array => napi_typedarray_type.int32_array,
            .Uint32Array => napi_typedarray_type.uint32_array,
            .Float32Array => napi_typedarray_type.float32_array,
            .Float64Array => napi_typedarray_type.float64_array,
            .BigInt64Array => napi_typedarray_type.bigint64_array,
            .BigUint64Array => napi_typedarray_type.biguint64_array,
            else => null,
        };
    }

    pub fn toJSType(this: napi_typedarray_type) jsc.JSValue.JSType {
        return switch (this) {
            .int8_array => .Int8Array,
            .uint8_array => .Uint8Array,
            .uint8_clamped_array => .Uint8ClampedArray,
            .int16_array => .Int16Array,
            .uint16_array => .Uint16Array,
            .int32_array => .Int32Array,
            .uint32_array => .Uint32Array,
            .float32_array => .Float32Array,
            .float64_array => .Float64Array,
            .bigint64_array => .BigInt64Array,
            .biguint64_array => .BigUint64Array,
        };
    }

    pub fn toC(this: napi_typedarray_type) jsc.C.JSTypedArrayType {
        return this.toJSType().toC();
    }
};

pub const NapiStatus = enum(c_uint) {
    ok = 0,
    invalid_arg = 1,
    object_expected = 2,
    string_expected = 3,
    name_expected = 4,
    function_expected = 5,
    number_expected = 6,
    boolean_expected = 7,
    array_expected = 8,
    generic_failure = 9,
    pending_exception = 10,
    cancelled = 11,
    escape_called_twice = 12,
    handle_scope_mismatch = 13,
    callback_scope_mismatch = 14,
    queue_full = 15,
    closing = 16,
    bigint_expected = 17,
    date_expected = 18,
    arraybuffer_expected = 19,
    detachable_arraybuffer_expected = 20,
    would_deadlock = 21,
};

/// This is not an `enum` so that the enum values cannot be trivially returned from NAPI functions,
/// as that would skip storing the last error code. You should wrap return values in a call to
/// napi_env.setLastError.
pub const napi_status = c_uint;

pub const napi_callback = ?*const fn (napi_env, napi_callback_info) callconv(.C) napi_value;

/// expects `napi_env`, `callback_data`, `context`
pub const napi_finalize = ?*const fn (napi_env, ?*anyopaque, ?*anyopaque) callconv(.C) void;
pub const napi_property_descriptor = extern struct {
    utf8name: [*c]const u8,
    name: napi_value,
    method: napi_callback,
    getter: napi_callback,
    setter: napi_callback,
    value: napi_value,
    attributes: napi_property_attributes,
    data: ?*anyopaque,
};
pub const napi_extended_error_info = extern struct {
    error_message: [*c]const u8,
    engine_reserved: ?*anyopaque,
    engine_error_code: u32,
    error_code: napi_status,
};

const napi_key_collection_mode = c_uint;
const napi_key_filter = c_uint;
const napi_key_conversion = c_uint;
const napi_type_tag = extern struct {
    lower: u64,
    upper: u64,
};
pub extern fn napi_get_last_error_info(env: napi_env, result: [*c][*c]const napi_extended_error_info) napi_status;
pub export fn napi_get_undefined(env_: napi_env, result_: ?*napi_value) napi_status {
    log("napi_get_undefined", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.set(env, .js_undefined);
    return env.ok();
}
pub export fn napi_get_null(env_: napi_env, result_: ?*napi_value) napi_status {
    log("napi_get_null", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.set(env, JSValue.jsNull());
    return env.ok();
}
pub extern fn napi_get_global(env: napi_env, result: *napi_value) napi_status;
pub export fn napi_get_boolean(env_: napi_env, value: bool, result_: ?*napi_value) napi_status {
    log("napi_get_boolean", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.set(env, JSValue.jsBoolean(value));
    return env.ok();
}
pub export fn napi_create_array(env_: napi_env, result_: ?*napi_value) napi_status {
    log("napi_create_array", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.set(env, JSValue.createEmptyArray(env.toJS(), 0) catch return env.setLastError(.pending_exception));
    return env.ok();
}
pub export fn napi_create_array_with_length(env_: napi_env, length: usize, result_: ?*napi_value) napi_status {
    log("napi_create_array_with_length", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };

    // JSC createEmptyArray takes u32
    // Node and V8 convert out-of-bounds array sizes to 0
    const len = std.math.cast(u32, length) orelse 0;

    const array = jsc.JSValue.createEmptyArray(env.toJS(), len) catch return env.setLastError(.pending_exception);
    array.ensureStillAlive();
    result.set(env, array);
    return env.ok();
}
pub extern fn napi_create_double(_: napi_env, value: f64, result: *napi_value) napi_status;
pub export fn napi_create_int32(env_: napi_env, value: i32, result_: ?*napi_value) napi_status {
    log("napi_create_int32", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.set(env, JSValue.jsNumber(value));
    return env.ok();
}
pub export fn napi_create_uint32(env_: napi_env, value: u32, result_: ?*napi_value) napi_status {
    log("napi_create_uint32", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.set(env, JSValue.jsNumber(value));
    return env.ok();
}
pub export fn napi_create_int64(env_: napi_env, value: i64, result_: ?*napi_value) napi_status {
    log("napi_create_int64", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.set(env, JSValue.jsNumber(value));
    return env.ok();
}
pub export fn napi_create_string_latin1(env_: napi_env, str: ?[*]const u8, length: usize, result_: ?*napi_value) napi_status {
    const env = env_ orelse {
        return envIsNull();
    };
    const result: *napi_value = result_ orelse {
        return env.invalidArg();
    };

    const slice: []const u8 = brk: {
        if (str) |ptr| {
            if (NAPI_AUTO_LENGTH == length) {
                break :brk bun.sliceTo(@as([*:0]const u8, @ptrCast(ptr)), 0);
            } else if (length > std.math.maxInt(i32)) {
                return env.invalidArg();
            } else {
                break :brk ptr[0..length];
            }
        }

        if (length == 0) {
            break :brk &.{};
        } else {
            return env.invalidArg();
        }
    };

    log("napi_create_string_latin1: {s}", .{slice});

    if (slice.len == 0) {
        result.set(env, bun.String.empty.toJS(env.toJS()));
        return env.ok();
    }

    var string, const bytes = bun.String.createUninitialized(.latin1, slice.len);
    defer string.deref();

    @memcpy(bytes, slice);

    result.set(env, string.toJS(env.toJS()));
    return env.ok();
}
pub export fn napi_create_string_utf8(env_: napi_env, str: ?[*]const u8, length: usize, result_: ?*napi_value) napi_status {
    const env = env_ orelse {
        return envIsNull();
    };
    const result: *napi_value = result_ orelse {
        return env.invalidArg();
    };

    const slice: []const u8 = brk: {
        if (str) |ptr| {
            if (NAPI_AUTO_LENGTH == length) {
                break :brk bun.sliceTo(@as([*:0]const u8, @ptrCast(ptr)), 0);
            } else if (length > std.math.maxInt(i32)) {
                return env.invalidArg();
            } else {
                break :brk ptr[0..length];
            }
        }

        if (length == 0) {
            break :brk &.{};
        } else {
            return env.invalidArg();
        }
    };

    log("napi_create_string_utf8: {s}", .{slice});

    const globalObject = env.toJS();
    const string = bun.String.createUTF8ForJS(globalObject, slice) catch return env.setLastError(.pending_exception);
    result.set(env, string);
    return env.ok();
}
pub export fn napi_create_string_utf16(env_: napi_env, str: ?[*]const char16_t, length: usize, result_: ?*napi_value) napi_status {
    const env = env_ orelse {
        return envIsNull();
    };
    const result: *napi_value = result_ orelse {
        return env.invalidArg();
    };

    const slice: []const u16 = brk: {
        if (str) |ptr| {
            if (NAPI_AUTO_LENGTH == length) {
                break :brk bun.sliceTo(@as([*:0]const u16, @ptrCast(ptr)), 0);
            } else if (length > std.math.maxInt(i32)) {
                return env.invalidArg();
            } else {
                break :brk ptr[0..length];
            }
        }

        if (length == 0) {
            break :brk &.{};
        } else {
            return env.invalidArg();
        }
    };

    if (comptime bun.Environment.allow_assert)
        log("napi_create_string_utf16: {d} {any}", .{ slice.len, bun.fmt.FormatUTF16{ .buf = slice[0..@min(slice.len, 512)] } });

    if (slice.len == 0) {
        result.set(env, bun.String.empty.toJS(env.toJS()));
        return env.ok();
    }

    var string, const chars = bun.String.createUninitialized(.utf16, slice.len);
    @memcpy(chars, slice);

    result.set(env, string.transferToJS(env.toJS()));
    return env.ok();
}

pub extern fn napi_create_symbol(env: napi_env, description: napi_value, result: *napi_value) napi_status;
pub extern fn napi_create_error(env: napi_env, code: napi_value, msg: napi_value, result: *napi_value) napi_status;
pub extern fn napi_create_type_error(env: napi_env, code: napi_value, msg: napi_value, result: *napi_value) napi_status;
pub extern fn napi_create_range_error(env: napi_env, code: napi_value, msg: napi_value, result: *napi_value) napi_status;
pub extern fn napi_typeof(env: napi_env, value: napi_value, result: *napi_valuetype) napi_status;
pub extern fn napi_get_value_double(env: napi_env, value: napi_value, result: *f64) napi_status;
pub extern fn napi_get_value_int32(_: napi_env, value_: napi_value, result: ?*i32) napi_status;
pub extern fn napi_get_value_uint32(_: napi_env, value_: napi_value, result_: ?*u32) napi_status;
pub extern fn napi_get_value_int64(_: napi_env, value_: napi_value, result_: ?*i64) napi_status;
pub extern fn napi_get_value_bool(_: napi_env, value_: napi_value, result_: ?*bool) napi_status;

pub extern fn napi_get_value_string_latin1(env: napi_env, value_: napi_value, buf_ptr_: ?[*:0]c_char, bufsize: usize, result_ptr: ?*usize) napi_status;

/// Copies a JavaScript string into a UTF-8 string buffer. The result is the
/// number of bytes (excluding the null terminator) copied into buf.
/// A sufficient buffer size should be greater than the length of string,
/// reserving space for null terminator.
/// If bufsize is insufficient, the string will be truncated and null terminated.
/// If buf is NULL, this method returns the length of the string (in bytes)
/// via the result parameter.
/// The result argument is optional unless buf is NULL.
pub extern fn napi_get_value_string_utf8(env: napi_env, value: napi_value, buf_ptr: [*c]u8, bufsize: usize, result_ptr: ?*usize) napi_status;
pub extern fn napi_get_value_string_utf16(env: napi_env, value_: napi_value, buf_ptr: ?[*]char16_t, bufsize: usize, result_ptr: ?*usize) napi_status;
pub extern fn napi_coerce_to_bool(env: napi_env, value_: napi_value, result_: ?*napi_value) napi_status;
pub extern fn napi_coerce_to_number(env: napi_env, value_: napi_value, result_: ?*napi_value) napi_status;
pub extern fn napi_coerce_to_object(env: napi_env, value_: napi_value, result_: ?*napi_value) napi_status;
pub export fn napi_get_prototype(env_: napi_env, object_: napi_value, result_: ?*napi_value) napi_status {
    log("napi_get_prototype", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    const object = object_.get();
    if (object == .zero) {
        return env.invalidArg();
    }
    if (!object.isObject()) {
        return env.setLastError(.object_expected);
    }

    result.set(env, JSValue.c(jsc.C.JSObjectGetPrototype(env.toJS().ref(), object.asObjectRef())));
    return env.ok();
}
// TODO: bind JSC::ownKeys
// pub export fn napi_get_property_names(env: napi_env, object: napi_value, result: *napi_value) napi_status {
// log("napi_get_property_names     ", .{});
// if (!object.isObject()) {
//         return .object_expected;
//     }

//     result.* =
// }
pub extern fn napi_set_element(env_: napi_env, object_: napi_value, index: c_uint, value_: napi_value) napi_status;
pub extern fn napi_has_element(env_: napi_env, object_: napi_value, index: c_uint, result_: ?*bool) napi_status;
pub extern fn napi_get_element(env: napi_env, object: napi_value, index: u32, result: *napi_value) napi_status;
pub extern fn napi_delete_element(env: napi_env, object: napi_value, index: u32, result: *napi_value) napi_status;
pub extern fn napi_define_properties(env: napi_env, object: napi_value, property_count: usize, properties: [*c]const napi_property_descriptor) napi_status;
pub export fn napi_is_array(env_: napi_env, value_: napi_value, result_: ?*bool) napi_status {
    log("napi_is_array", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    const value = value_.get();
    result.* = value.jsType().isArray();
    return env.ok();
}
pub export fn napi_get_array_length(env_: napi_env, value_: napi_value, result_: [*c]u32) napi_status {
    log("napi_get_array_length", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    const value = value_.get();

    if (!value.jsType().isArray()) {
        return env.setLastError(.array_expected);
    }

    result.* = @truncate(value.getLength(env.toJS()) catch return env.setLastError(.pending_exception));
    return env.ok();
}
pub export fn napi_strict_equals(env_: napi_env, lhs_: napi_value, rhs_: napi_value, result_: ?*bool) napi_status {
    log("napi_strict_equals", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    const lhs, const rhs = .{ lhs_.get(), rhs_.get() };
    // TODO: this needs to be strictEquals not isSameValue (NaN !== NaN and -0 === 0)
    result.* = lhs.isSameValue(rhs, env.toJS()) catch return env.setLastError(.pending_exception);
    return env.ok();
}
pub extern fn napi_call_function(env: napi_env, recv: napi_value, func: napi_value, argc: usize, argv: [*c]const napi_value, result: *napi_value) napi_status;
pub extern fn napi_new_instance(env: napi_env, constructor: napi_value, argc: usize, argv: [*c]const napi_value, result_: ?*napi_value) napi_status;
pub extern fn napi_instanceof(env_: napi_env, object_: napi_value, constructor_: napi_value, result_: ?*bool) napi_status;
pub extern fn napi_get_cb_info(env: napi_env, cbinfo: napi_callback_info, argc: [*c]usize, argv: *napi_value, this_arg: *napi_value, data: [*]*anyopaque) napi_status;
pub extern fn napi_get_new_target(env: napi_env, cbinfo: napi_callback_info, result: *napi_value) napi_status;
pub extern fn napi_define_class(
    env: napi_env,
    utf8name: [*c]const u8,
    length: usize,
    constructor: napi_callback,
    data: ?*anyopaque,
    property_count: usize,
    properties: [*c]const napi_property_descriptor,
    result: *napi_value,
) napi_status;
pub extern fn napi_wrap(env: napi_env, js_object: napi_value, native_object: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_ref) napi_status;
pub extern fn napi_unwrap(env: napi_env, js_object: napi_value, result: [*]*anyopaque) napi_status;
pub extern fn napi_remove_wrap(env: napi_env, js_object: napi_value, result: [*]*anyopaque) napi_status;
pub extern fn napi_create_object(env: napi_env, result: *napi_value) napi_status;
pub extern fn napi_create_external(env: napi_env, data: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_value) napi_status;
pub extern fn napi_get_value_external(env: napi_env, value: napi_value, result: [*]*anyopaque) napi_status;
pub extern fn napi_create_reference(env: napi_env, value: napi_value, initial_refcount: u32, result: *napi_ref) napi_status;
pub extern fn napi_delete_reference(env: napi_env, ref: napi_ref) napi_status;
pub extern fn napi_reference_ref(env: napi_env, ref: napi_ref, result: [*c]u32) napi_status;
pub extern fn napi_reference_unref(env: napi_env, ref: napi_ref, result: [*c]u32) napi_status;
pub extern fn napi_get_reference_value(env: napi_env, ref: napi_ref, result: *napi_value) napi_status;

pub export fn napi_open_handle_scope(env_: napi_env, result_: ?*napi_handle_scope) napi_status {
    log("napi_open_handle_scope", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.* = NapiHandleScope.open(env, false);
    return env.ok();
}

pub export fn napi_close_handle_scope(env_: napi_env, handle_scope: napi_handle_scope) napi_status {
    log("napi_close_handle_scope", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    if (handle_scope) |scope| {
        scope.close(env);
    }

    return env.ok();
}

// we don't support async contexts
pub export fn napi_async_init(env_: napi_env, _: napi_value, _: napi_value, async_ctx: **anyopaque) napi_status {
    log("napi_async_init", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    async_ctx.* = env;
    return env.ok();
}

// we don't support async contexts
pub export fn napi_async_destroy(env_: napi_env, _: *anyopaque) napi_status {
    log("napi_async_destroy", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    return env.ok();
}

// this is just a regular function call
pub export fn napi_make_callback(env_: napi_env, _: *anyopaque, recv_: napi_value, func_: napi_value, arg_count: usize, args: ?[*]const napi_value, maybe_result: ?*napi_value) napi_status {
    log("napi_make_callback", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const recv, const func = .{ recv_.get(), func_.get() };
    if (func.isEmptyOrUndefinedOrNull() or !func.isCallable()) {
        return env.setLastError(.function_expected);
    }

    const res = func.call(
        env.toJS(),
        if (recv != .zero)
            recv
        else
            .js_undefined,
        if (arg_count > 0 and args != null)
            @as([*]const jsc.JSValue, @ptrCast(args.?))[0..arg_count]
        else
            &.{},
    ) catch |err| // TODO: handle errors correctly
        env.toJS().takeException(err);

    if (maybe_result) |result| {
        result.set(env, res);
    }

    // TODO: this is likely incorrect
    if (res.isAnyError()) {
        return env.setLastError(.pending_exception);
    }

    return env.ok();
}

// Sometimes shared libraries reference symbols which are not used
// We don't want to fail to load the library because of that
// so we instead return an error and warn the user
fn notImplementedYet(comptime name: []const u8) void {
    bun.onceUnsafe(
        struct {
            pub fn warn() void {
                if (jsc.VirtualMachine.get().log.level.atLeast(.warn)) {
                    bun.Output.prettyErrorln("<r><yellow>warning<r><d>:<r> Node-API function <b>\"{s}\"<r> is not implemented yet.\n Track the status of Node-API in Bun: https://github.com/oven-sh/bun/issues/158", .{name});
                    bun.Output.flush();
                }
            }
        }.warn,
        void,
    );
}

pub export fn napi_open_escapable_handle_scope(env_: napi_env, result_: ?*napi_escapable_handle_scope) napi_status {
    log("napi_open_escapable_handle_scope", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    result.* = NapiHandleScope.open(env, true);
    return env.ok();
}
pub export fn napi_close_escapable_handle_scope(env_: napi_env, scope: napi_escapable_handle_scope) napi_status {
    log("napi_close_escapable_handle_scope", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    if (scope) |s| {
        s.close(env);
    }
    return env.ok();
}
pub export fn napi_escape_handle(env_: napi_env, scope_: napi_escapable_handle_scope, escapee: napi_value, result_: ?*napi_value) napi_status {
    log("napi_escape_handle", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    const scope = scope_ orelse {
        return env.invalidArg();
    };
    scope.escape(escapee.get()) catch return env.setLastError(.escape_called_twice);
    result.* = escapee;
    return env.ok();
}
pub extern fn napi_type_tag_object(env: napi_env, _: napi_value, _: [*c]const napi_type_tag) napi_status;
pub extern fn napi_check_object_type_tag(env: napi_env, _: napi_value, _: [*c]const napi_type_tag, _: *bool) napi_status;

// do nothing for both of these
pub export fn napi_open_callback_scope(env_: napi_env, _: napi_value, _: *anyopaque, _: *anyopaque) napi_status {
    log("napi_open_callback_scope", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    return env.ok();
}
pub export fn napi_close_callback_scope(env_: napi_env, _: *anyopaque) napi_status {
    log("napi_close_callback_scope", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    return env.ok();
}
pub extern fn napi_throw(env: napi_env, @"error": napi_value) napi_status;
pub extern fn napi_throw_error(env: napi_env, code: [*c]const u8, msg: [*c]const u8) napi_status;
pub extern fn napi_throw_type_error(env: napi_env, code: [*c]const u8, msg: [*c]const u8) napi_status;
pub extern fn napi_throw_range_error(env: napi_env, code: [*c]const u8, msg: [*c]const u8) napi_status;
pub export fn napi_is_error(env_: napi_env, value_: napi_value, result: *bool) napi_status {
    log("napi_is_error", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const value = value_.get();
    result.* = value.isAnyError();
    return env.ok();
}
pub extern fn napi_is_exception_pending(env: napi_env, result: *bool) napi_status;
pub extern fn napi_get_and_clear_last_exception(env: napi_env, result: *napi_value) napi_status;
pub export fn napi_is_arraybuffer(env_: napi_env, value_: napi_value, result_: ?*bool) napi_status {
    log("napi_is_arraybuffer", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const result = result_ orelse {
        return env.invalidArg();
    };
    const value = value_.get();
    result.* = !value.isNumber() and value.jsTypeLoose() == .ArrayBuffer;
    return env.ok();
}
pub extern fn napi_create_arraybuffer(env: napi_env, byte_length: usize, data: [*]const u8, result: *napi_value) napi_status;

pub extern fn napi_create_external_arraybuffer(env: napi_env, external_data: ?*anyopaque, byte_length: usize, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_value) napi_status;

pub export fn napi_get_arraybuffer_info(env_: napi_env, arraybuffer_: napi_value, data: ?*[*]u8, byte_length: ?*usize) napi_status {
    log("napi_get_arraybuffer_info", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const arraybuffer = arraybuffer_.get();
    const array_buffer = arraybuffer.asArrayBuffer(env.toJS()) orelse return env.setLastError(.invalid_arg);
    if (array_buffer.typed_array_type != .ArrayBuffer) {
        return env.setLastError(.invalid_arg);
    }

    const slice = array_buffer.slice();
    if (data) |dat|
        dat.* = slice.ptr;
    if (byte_length) |len|
        len.* = slice.len;
    return env.ok();
}

pub extern fn napi_is_typedarray(napi_env, napi_value, *bool) napi_status;

pub export fn napi_get_typedarray_info(
    env_: napi_env,
    typedarray_: napi_value,
    maybe_type: ?*napi_typedarray_type,
    maybe_length: ?*usize,
    maybe_data: ?*[*]u8,
    maybe_arraybuffer: ?*napi_value,
    maybe_byte_offset: ?*usize,
) napi_status {
    log("napi_get_typedarray_info", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const typedarray = typedarray_.get();
    if (typedarray.isEmptyOrUndefinedOrNull())
        return env.invalidArg();
    defer typedarray.ensureStillAlive();

    const array_buffer = typedarray.asArrayBuffer(env.toJS()) orelse return env.invalidArg();
    if (maybe_type) |@"type"|
        @"type".* = array_buffer.typed_array_type.toTypedArrayType().toNapi() orelse return env.invalidArg();

    // TODO: handle detached
    if (maybe_data) |data|
        data.* = array_buffer.ptr;

    if (maybe_length) |length|
        length.* = array_buffer.len;

    if (maybe_arraybuffer) |arraybuffer|
        arraybuffer.set(env, JSValue.c(jsc.C.JSObjectGetTypedArrayBuffer(env.toJS().ref(), typedarray.asObjectRef(), null)));

    if (maybe_byte_offset) |byte_offset|
        byte_offset.* = array_buffer.offset;
    return env.ok();
}
pub extern fn napi_create_dataview(env: napi_env, length: usize, arraybuffer: napi_value, byte_offset: usize, result: *napi_value) napi_status;
pub export fn napi_is_dataview(env_: napi_env, value_: napi_value, result_: ?*bool) napi_status {
    log("napi_is_dataview", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    const value = value_.get();
    result.* = !value.isEmptyOrUndefinedOrNull() and value.jsTypeLoose() == .DataView;
    return env.ok();
}
pub export fn napi_get_dataview_info(
    env_: napi_env,
    dataview_: napi_value,
    maybe_bytelength: ?*usize,
    maybe_data: ?*[*]u8,
    maybe_arraybuffer: ?*napi_value,
    maybe_byte_offset: ?*usize,
) napi_status {
    log("napi_get_dataview_info", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const dataview = dataview_.get();
    const array_buffer = dataview.asArrayBuffer(env.toJS()) orelse return env.setLastError(.object_expected);
    if (maybe_bytelength) |bytelength|
        bytelength.* = array_buffer.byte_len;

    if (maybe_data) |data|
        data.* = array_buffer.ptr;

    if (maybe_arraybuffer) |arraybuffer|
        arraybuffer.set(env, JSValue.c(jsc.C.JSObjectGetTypedArrayBuffer(env.toJS().ref(), dataview.asObjectRef(), null)));

    if (maybe_byte_offset) |byte_offset|
        byte_offset.* = array_buffer.offset;

    return env.ok();
}
pub export fn napi_get_version(env_: napi_env, result_: ?*u32) napi_status {
    log("napi_get_version", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    // The result is supposed to be the highest NAPI version Bun supports, rather than the version reported by a NAPI module.
    result.* = 9;
    return env.ok();
}
pub export fn napi_create_promise(env_: napi_env, deferred_: ?*napi_deferred, promise_: ?*napi_value) napi_status {
    log("napi_create_promise", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const deferred = deferred_ orelse {
        return env.invalidArg();
    };
    const promise = promise_ orelse {
        return env.invalidArg();
    };
    deferred.* = bun.default_allocator.create(jsc.JSPromise.Strong) catch @panic("failed to allocate napi_deferred");
    deferred.*.* = jsc.JSPromise.Strong.init(env.toJS());
    promise.set(env, deferred.*.get().asValue(env.toJS()));
    return env.ok();
}
pub export fn napi_resolve_deferred(env_: napi_env, deferred: napi_deferred, resolution_: napi_value) napi_status {
    log("napi_resolve_deferred", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const resolution = resolution_.get();
    var prom = deferred.get();
    prom.resolve(env.toJS(), resolution);
    deferred.deinit();
    bun.default_allocator.destroy(deferred);
    return env.ok();
}
pub export fn napi_reject_deferred(env_: napi_env, deferred: napi_deferred, rejection_: napi_value) napi_status {
    log("napi_reject_deferred", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const rejection = rejection_.get();
    var prom = deferred.get();
    prom.reject(env.toJS(), rejection);
    deferred.deinit();
    bun.default_allocator.destroy(deferred);
    return env.ok();
}
pub export fn napi_is_promise(env_: napi_env, value_: napi_value, is_promise_: ?*bool) napi_status {
    log("napi_is_promise", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const value = value_.get();
    const is_promise = is_promise_ orelse {
        return env.invalidArg();
    };

    if (value == .zero) {
        return env.invalidArg();
    }

    is_promise.* = value.asAnyPromise() != null;
    return env.ok();
}
pub extern fn napi_run_script(env: napi_env, script: napi_value, result: *napi_value) napi_status;
pub extern fn napi_adjust_external_memory(env: napi_env, change_in_bytes: i64, adjusted_value: [*c]i64) napi_status;
pub export fn napi_create_date(env_: napi_env, time: f64, result_: ?*napi_value) napi_status {
    log("napi_create_date", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    var args = [_]jsc.C.JSValueRef{jsc.JSValue.jsNumber(time).asObjectRef()};
    result.set(env, JSValue.c(jsc.C.JSObjectMakeDate(env.toJS().ref(), 1, &args, TODO_EXCEPTION)));
    return env.ok();
}
pub export fn napi_is_date(env_: napi_env, value_: napi_value, is_date_: ?*bool) napi_status {
    log("napi_is_date", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    env.checkGC();
    const is_date = is_date_ orelse {
        return env.invalidArg();
    };
    const value = value_.get();
    is_date.* = value.jsTypeLoose() == .JSDate;
    return env.ok();
}
pub extern fn napi_get_date_value(env: napi_env, value: napi_value, result: *f64) napi_status;
pub extern fn napi_add_finalizer(env: napi_env, js_object: napi_value, native_object: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: napi_ref) napi_status;
pub extern fn napi_create_bigint_int64(env: napi_env, value: i64, result_: ?*napi_value) napi_status;
pub extern fn napi_create_bigint_uint64(env: napi_env, value: u64, result_: ?*napi_value) napi_status;
pub extern fn napi_create_bigint_words(env: napi_env, sign_bit: c_int, word_count: usize, words: [*c]const u64, result: *napi_value) napi_status;
pub extern fn napi_get_value_bigint_int64(_: napi_env, value_: napi_value, result_: ?*i64, _: *bool) napi_status;
pub extern fn napi_get_value_bigint_uint64(_: napi_env, value_: napi_value, result_: ?*u64, _: *bool) napi_status;

pub extern fn napi_get_value_bigint_words(env: napi_env, value: napi_value, sign_bit: [*c]c_int, word_count: [*c]usize, words: [*c]u64) napi_status;
pub extern fn napi_get_all_property_names(env: napi_env, object: napi_value, key_mode: napi_key_collection_mode, key_filter: napi_key_filter, key_conversion: napi_key_conversion, result: *napi_value) napi_status;
pub extern fn napi_set_instance_data(env: napi_env, data: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque) napi_status;
pub extern fn napi_get_instance_data(env: napi_env, data: [*]*anyopaque) napi_status;
pub extern fn napi_detach_arraybuffer(env: napi_env, arraybuffer: napi_value) napi_status;
pub extern fn napi_is_detached_arraybuffer(env: napi_env, value: napi_value, result: *bool) napi_status;

/// must be globally allocated
pub const napi_async_work = struct {
    task: WorkPoolTask = .{ .callback = &runFromThreadPool },
    concurrent_task: jsc.ConcurrentTask = .{},
    event_loop: *jsc.EventLoop,
    global: *jsc.JSGlobalObject,
    env: *NapiEnv,
    execute: napi_async_execute_callback,
    complete: ?napi_async_complete_callback,
    data: ?*anyopaque = null,
    status: std.atomic.Value(Status) = .init(.pending),
    scheduled: bool = false,
    poll_ref: Async.KeepAlive = .{},

    pub const Status = enum(u32) {
        pending = 0,
        started = 1,
        completed = 2,
        cancelled = 3,
    };

    pub fn new(env: *NapiEnv, execute: napi_async_execute_callback, complete: ?napi_async_complete_callback, data: ?*anyopaque) *napi_async_work {
        const global = env.toJS();

        const work = bun.new(napi_async_work, .{
            .global = global,
            .env = env,
            .execute = execute,
            .event_loop = global.bunVM().eventLoop(),
            .complete = complete,
            .data = data,
        });
        return work;
    }

    pub fn destroy(this: *napi_async_work) void {
        bun.destroy(this);
    }

    pub fn schedule(this: *napi_async_work) void {
        if (this.scheduled) return;
        this.scheduled = true;
        this.poll_ref.ref(this.global.bunVM());
        WorkPool.schedule(&this.task);
    }

    pub fn runFromThreadPool(task: *WorkPoolTask) void {
        var this: *napi_async_work = @fieldParentPtr("task", task);
        this.run();
    }
    fn run(this: *napi_async_work) void {
        if (this.status.cmpxchgStrong(.pending, .started, .seq_cst, .seq_cst)) |state| {
            if (state == .cancelled) {
                this.event_loop.enqueueTaskConcurrent(this.concurrent_task.from(this, .manual_deinit));
                return;
            }
        }
        this.execute(this.env, this.data);
        this.status.store(.completed, .seq_cst);

        this.event_loop.enqueueTaskConcurrent(this.concurrent_task.from(this, .manual_deinit));
    }

    pub fn cancel(this: *napi_async_work) bool {
        return this.status.cmpxchgStrong(.pending, .cancelled, .seq_cst, .seq_cst) == null;
    }

    pub fn runFromJS(this: *napi_async_work, vm: *jsc.VirtualMachine, global: *jsc.JSGlobalObject) void {
        // Note: the "this" value here may already be freed by the user in `complete`
        var poll_ref = this.poll_ref;
        defer poll_ref.unref(vm);

        // https://github.com/nodejs/node/blob/a2de5b9150da60c77144bb5333371eaca3fab936/src/node_api.cc#L1201
        const complete = this.complete orelse {
            return;
        };

        const env = this.env;
        const handle_scope = NapiHandleScope.open(env, false);
        defer if (handle_scope) |scope| scope.close(env);

        const status: NapiStatus = if (this.status.load(.seq_cst) == .cancelled)
            .cancelled
        else
            .ok;

        complete(
            env,
            @intFromEnum(status),
            this.data,
        );

        if (global.hasException()) {
            global.reportActiveExceptionAsUnhandled(error.JSError);
        }
    }
};
pub const napi_threadsafe_function = *ThreadSafeFunction;
pub const napi_threadsafe_function_release_mode = enum(c_uint) {
    release = 0,
    abort = 1,
};
pub const napi_tsfn_nonblocking = 0;
pub const napi_tsfn_blocking = 1;
pub const napi_threadsafe_function_call_mode = c_uint;
pub const napi_async_execute_callback = *const fn (napi_env, ?*anyopaque) callconv(.C) void;
pub const napi_async_complete_callback = *const fn (napi_env, napi_status, ?*anyopaque) callconv(.C) void;
pub const napi_threadsafe_function_call_js = *const fn (napi_env, napi_value, ?*anyopaque, ?*anyopaque) callconv(.C) void;
pub const napi_node_version = extern struct {
    major: u32,
    minor: u32,
    patch: u32,
    release: [*:0]const u8,

    const parsed_nodejs_version = std.SemanticVersion.parse(bun.Environment.reported_nodejs_version) catch @panic("Invalid reported Node.js version");

    pub const global: napi_node_version = .{
        .major = parsed_nodejs_version.major,
        .minor = parsed_nodejs_version.minor,
        .patch = parsed_nodejs_version.patch,
        .release = "node",
    };
};
pub const struct_napi_async_cleanup_hook_handle__ = opaque {};
pub const napi_async_cleanup_hook_handle = ?*struct_napi_async_cleanup_hook_handle__;
pub const napi_async_cleanup_hook = ?*const fn (napi_async_cleanup_hook_handle, ?*anyopaque) callconv(.C) void;

pub const napi_addon_register_func = *const fn (napi_env, napi_value) callconv(.C) napi_value;
pub const struct_napi_module = extern struct {
    nm_version: c_int,
    nm_flags: c_uint,
    nm_filename: [*c]const u8,
    nm_register_func: napi_addon_register_func,
    nm_modname: [*c]const u8,
    nm_priv: ?*anyopaque,
    reserved: [4]?*anyopaque,
};
pub const napi_module = struct_napi_module;
fn napiSpan(ptr: anytype, len: usize) []const u8 {
    if (ptr == null)
        return &[_]u8{};

    if (len == NAPI_AUTO_LENGTH) {
        return bun.sliceTo(ptr.?, 0);
    }

    return ptr.?[0..len];
}
pub export fn napi_fatal_error(location_ptr: ?[*:0]const u8, location_len: usize, message_ptr: ?[*:0]const u8, message_len_: usize) noreturn {
    log("napi_fatal_error", .{});
    napi_internal_suppress_crash_on_abort_if_desired();
    var message = napiSpan(message_ptr, message_len_);
    if (message.len == 0) {
        message = "fatal error";
    }

    const location = napiSpan(location_ptr, location_len);
    if (location.len > 0) {
        bun.Output.panic("NAPI FATAL ERROR: {s} {s}", .{ location, message });
    }

    bun.Output.panic("napi: {s}", .{message});
}
pub extern fn napi_create_buffer(env: napi_env, length: usize, data: ?**anyopaque, result: *napi_value) napi_status;
pub extern fn napi_create_external_buffer(env: napi_env, length: usize, data: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_value) napi_status;
pub export fn napi_create_buffer_copy(env_: napi_env, length: usize, data: [*]u8, result_data: ?*?*anyopaque, result_: ?*napi_value) napi_status {
    log("napi_create_buffer_copy: {d}", .{length});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    var buffer = jsc.JSValue.createBufferFromLength(env.toJS(), length) catch return env.setLastError(.pending_exception);
    if (buffer.asArrayBuffer(env.toJS())) |array_buf| {
        if (length > 0) {
            @memcpy(array_buf.slice()[0..length], data[0..length]);
        }
        if (result_data) |ptr| {
            ptr.* = if (length > 0) array_buf.ptr else null;
        }
    }

    result.set(env, buffer);

    return env.ok();
}
extern fn napi_is_buffer(napi_env, napi_value, *bool) napi_status;
pub export fn napi_get_buffer_info(env_: napi_env, value_: napi_value, data: ?*[*]u8, length: ?*usize) napi_status {
    log("napi_get_buffer_info", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const value = value_.get();
    const array_buf = value.asArrayBuffer(env.toJS()) orelse {
        return env.setLastError(.invalid_arg);
    };

    if (data) |dat|
        dat.* = array_buf.ptr;

    if (length) |len|
        len.* = array_buf.byte_len;

    return env.ok();
}

extern fn node_api_create_syntax_error(napi_env, napi_value, napi_value, *napi_value) napi_status;
extern fn node_api_symbol_for(napi_env, [*]const c_char, usize, *napi_value) napi_status;
extern fn node_api_throw_syntax_error(napi_env, [*]const c_char, [*]const c_char) napi_status;
extern fn node_api_create_external_string_latin1(napi_env, [*:0]u8, usize, napi_finalize, ?*anyopaque, *JSValue, *bool) napi_status;
extern fn node_api_create_external_string_utf16(napi_env, [*:0]u16, usize, napi_finalize, ?*anyopaque, *JSValue, *bool) napi_status;

pub export fn napi_create_async_work(
    env_: napi_env,
    _: napi_value,
    _: [*:0]const u8,
    execute_: ?napi_async_execute_callback,
    complete: ?napi_async_complete_callback,
    data: ?*anyopaque,
    result_: ?**napi_async_work,
) napi_status {
    log("napi_create_async_work", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    // https://github.com/nodejs/node/blob/a2de5b9150da60c77144bb5333371eaca3fab936/src/node_api.cc#L1245
    const execute = execute_ orelse {
        return env.invalidArg();
    };
    result.* = napi_async_work.new(env, execute, complete, data);
    return env.ok();
}
pub export fn napi_delete_async_work(env_: napi_env, work_: ?*napi_async_work) napi_status {
    log("napi_delete_async_work", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const work = work_ orelse {
        return env.invalidArg();
    };
    if (comptime bun.Environment.allow_assert) bun.assert(env.toJS() == work.global);
    work.destroy();
    return env.ok();
}
pub export fn napi_queue_async_work(env_: napi_env, work_: ?*napi_async_work) napi_status {
    log("napi_queue_async_work", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const work = work_ orelse {
        return env.invalidArg();
    };
    if (comptime bun.Environment.allow_assert) bun.assert(env.toJS() == work.global);
    work.schedule();
    return env.ok();
}
pub export fn napi_cancel_async_work(env_: napi_env, work_: ?*napi_async_work) napi_status {
    log("napi_cancel_async_work", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const work = work_ orelse {
        return env.invalidArg();
    };
    if (comptime bun.Environment.allow_assert) bun.assert(env.toJS() == work.global);
    if (work.cancel()) {
        return env.ok();
    }

    return env.genericFailure();
}
pub export fn napi_get_node_version(env_: napi_env, version_: ?**const napi_node_version) napi_status {
    log("napi_get_node_version", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const version = version_ orelse {
        return env.invalidArg();
    };
    version.* = &napi_node_version.global;
    return env.ok();
}
const napi_event_loop = if (bun.Environment.isWindows) *bun.windows.libuv.Loop else *jsc.EventLoop;
pub export fn napi_get_uv_event_loop(env_: napi_env, loop_: ?*napi_event_loop) napi_status {
    log("napi_get_uv_event_loop", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const loop = loop_ orelse {
        return env.invalidArg();
    };
    if (bun.Environment.isWindows) {
        // alignment error is incorrect.
        // TODO(@190n) investigate
        @setRuntimeSafety(false);
        loop.* = jsc.VirtualMachine.get().uvLoop();
    } else {
        // there is no uv event loop on posix, we use our event loop handle.
        loop.* = env.toJS().bunVM().eventLoop();
    }
    return env.ok();
}
pub extern fn napi_fatal_exception(env: napi_env, err: napi_value) napi_status;
pub extern fn napi_add_async_cleanup_hook(env: napi_env, function: napi_async_cleanup_hook, data: ?*anyopaque, handle_out: ?*napi_async_cleanup_hook_handle) napi_status;
pub extern fn napi_add_env_cleanup_hook(env: napi_env, function: ?*const fn (?*anyopaque) void, data: ?*anyopaque) napi_status;
pub extern fn napi_create_typedarray(env: napi_env, napi_typedarray_type, length: usize, arraybuffer: napi_value, byte_offset: usize, result: ?*napi_value) napi_status;
pub extern fn napi_remove_async_cleanup_hook(handle: napi_async_cleanup_hook_handle) napi_status;
pub extern fn napi_remove_env_cleanup_hook(env: napi_env, function: ?*const fn (?*anyopaque) void, data: ?*anyopaque) napi_status;

extern fn napi_internal_cleanup_env_cpp(env: napi_env) callconv(.C) void;
extern fn napi_internal_check_gc(env: napi_env) callconv(.C) void;

pub export fn napi_internal_register_cleanup_zig(env_: napi_env) void {
    const env = env_.?;
    env.toJS().bunVM().rareData().pushCleanupHook(env.toJS(), env, struct {
        fn callback(data: ?*anyopaque) callconv(.C) void {
            napi_internal_cleanup_env_cpp(@ptrCast(data));
        }
    }.callback);
}

pub export fn napi_internal_suppress_crash_on_abort_if_desired() void {
    if (bun.getRuntimeFeatureFlag(.BUN_INTERNAL_SUPPRESS_CRASH_ON_NAPI_ABORT)) {
        bun.crash_handler.suppressReporting();
    }
}

extern fn napi_internal_remove_finalizer(env: napi_env, fun: napi_finalize, hint: ?*anyopaque, data: ?*anyopaque) callconv(.C) void;

pub const Finalizer = struct {
    env: napi_env,
    fun: napi_finalize,
    data: ?*anyopaque = null,
    hint: ?*anyopaque = null,

    pub fn run(this: *Finalizer) void {
        const env = this.env.?;
        const handle_scope = NapiHandleScope.open(env, false);
        defer if (handle_scope) |scope| scope.close(env);
        if (this.fun) |fun| {
            fun(env, this.data, this.hint);
        }
        napi_internal_remove_finalizer(env, this.fun, this.hint, this.data);
        if (env.toJS().tryTakeException()) |exception| {
            _ = env.toJS().bunVM().uncaughtException(env.toJS(), exception, false);
        }
    }

    /// For Node-API modules not built with NAPI_EXPERIMENTAL, finalizers should be deferred to the
    /// immediate task queue instead of run immediately. This lets finalizers perform allocations,
    /// which they couldn't if they ran immediately while the garbage collector is still running.
    pub export fn napi_internal_enqueue_finalizer(env: napi_env, fun: napi_finalize, data: ?*anyopaque, hint: ?*anyopaque) callconv(.C) void {
        const task = NapiFinalizerTask.init(.{ .env = env, .fun = fun, .data = data, .hint = hint });
        task.schedule();
    }
};

// TODO: generate comptime version of this instead of runtime checking
pub const ThreadSafeFunction = struct {
    pub const Callback = union(enum) {
        js: jsc.Strong.Optional,
        c: struct {
            js: jsc.Strong.Optional,
            napi_threadsafe_function_call_js: napi_threadsafe_function_call_js,
        },

        pub fn deinit(this: *Callback) void {
            if (this.* == .js) {
                this.js.deinit();
            } else if (this.* == .c) {
                this.c.js.deinit();
            }
        }
    };
    /// thread-safe functions can be "referenced" and "unreferenced". A
    /// "referenced" thread-safe function will cause the event loop on the thread
    /// on which it is created to remain alive until the thread-safe function is
    /// destroyed. In contrast, an "unreferenced" thread-safe function will not
    /// prevent the event loop from exiting. The APIs napi_ref_threadsafe_function
    /// and napi_unref_threadsafe_function exist for this purpose.
    ///
    /// Neither does napi_unref_threadsafe_function mark the thread-safe
    /// functions as able to be destroyed nor does napi_ref_threadsafe_function
    /// prevent it from being destroyed.
    poll_ref: Async.KeepAlive,

    // User implementation error can cause this number to go negative.
    thread_count: std.atomic.Value(i64) = std.atomic.Value(i64).init(0),
    // for std.condvar
    lock: std.Thread.Mutex = .{},

    event_loop: *jsc.EventLoop,
    tracker: jsc.Debugger.AsyncTaskTracker,

    env: *NapiEnv,

    finalizer: Finalizer = Finalizer{ .env = null, .fun = null, .data = null },
    has_queued_finalizer: bool = false,
    queue: Queue = .{
        .data = std.fifo.LinearFifo(?*anyopaque, .Dynamic).init(bun.default_allocator),
        .max_queue_size = 0,
    },

    ctx: ?*anyopaque = null,

    callback: Callback = undefined,
    dispatch_state: DispatchState.Atomic = DispatchState.Atomic.init(.idle),
    blocking_condvar: std.Thread.Condition = .{},
    closing: std.atomic.Value(ClosingState) = std.atomic.Value(ClosingState).init(.not_closing),
    aborted: std.atomic.Value(bool) = std.atomic.Value(bool).init(true),

    pub const new = bun.TrivialNew(ThreadSafeFunction);

    const ClosingState = enum(u8) {
        not_closing,
        closing,
        closed,
    };

    pub const DispatchState = enum(u8) {
        idle,
        running,
        pending,

        pub const Atomic = std.atomic.Value(DispatchState);
    };

    pub const Queue = struct {
        data: std.fifo.LinearFifo(?*anyopaque, .Dynamic),

        /// This value will never change after initialization. Zero means the size is unlimited.
        max_queue_size: usize,

        count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),

        pub fn init(max_queue_size: usize, allocator: std.mem.Allocator) Queue {
            return .{ .data = std.fifo.LinearFifo(?*anyopaque, .Dynamic).init(allocator), .max_queue_size = max_queue_size };
        }

        pub fn deinit(this: *Queue) void {
            this.data.deinit();
        }

        pub fn isBlocked(this: *const Queue) bool {
            return this.max_queue_size > 0 and this.count.load(.seq_cst) >= this.max_queue_size;
        }
    };

    // This has two states:
    // 1. We need to run potentially multiple tasks.
    // 2. We need to finalize the ThreadSafeFunction.
    pub fn onDispatch(this: *ThreadSafeFunction) void {
        if (this.closing.load(.seq_cst) == .closed) {
            // Finalize the ThreadSafeFunction.
            this.deinit();
            return;
        }

        var is_first = true;

        // Run the tasks.
        while (true) {
            this.dispatch_state.store(.running, .seq_cst);
            if (this.dispatchOne(is_first)) {
                is_first = false;
                this.dispatch_state.store(.pending, .seq_cst);
            } else {
                // We're done running tasks, for now.
                this.dispatch_state.store(.idle, .seq_cst);
                break;
            }
        }

        // Node sets a maximum number of runs per ThreadSafeFunction to 1,000.
        // We don't set a max. I would like to see an issue caused by not
        // setting a max before we do set a max. It is better for performance to
        // not add unnecessary event loop ticks.
    }

    pub fn isClosing(this: *const ThreadSafeFunction) bool {
        return this.closing.load(.seq_cst) != .not_closing;
    }

    fn maybeQueueFinalizer(this: *ThreadSafeFunction) void {
        switch (this.closing.swap(.closed, .seq_cst)) {
            .closing, .not_closing => {
                // TODO: is this boolean necessary? Can we rely just on the closing value?
                if (!this.has_queued_finalizer) {
                    this.has_queued_finalizer = true;
                    this.callback.deinit();
                    this.poll_ref.disable();
                    this.event_loop.enqueueTask(jsc.Task.init(this));
                }
            },
            .closed => {
                // already scheduled.
            },
        }
    }

    pub fn dispatchOne(this: *ThreadSafeFunction, is_first: bool) bool {
        var queue_finalizer_after_call = false;
        const has_more, const task = brk: {
            this.lock.lock();
            defer this.lock.unlock();
            const was_blocked = this.queue.isBlocked();
            const t = this.queue.data.readItem() orelse {
                // When there are no tasks and the number of threads that have
                // references reaches zero, we prepare to finalize the
                // ThreadSafeFunction.
                if (this.thread_count.load(.seq_cst) == 0) {
                    if (this.queue.max_queue_size > 0) {
                        this.blocking_condvar.signal();
                    }
                    this.maybeQueueFinalizer();
                }
                return false;
            };

            if (this.queue.count.fetchSub(1, .seq_cst) == 1 and this.thread_count.load(.seq_cst) == 0) {
                this.closing.store(.closing, .seq_cst);
                if (this.queue.max_queue_size > 0) {
                    this.blocking_condvar.signal();
                }
                queue_finalizer_after_call = true;
            } else if (was_blocked and !this.queue.isBlocked()) {
                this.blocking_condvar.signal();
            }

            break :brk .{ !this.isClosing(), t };
        };

        this.call(task, !is_first) catch return false;

        if (queue_finalizer_after_call) {
            this.maybeQueueFinalizer();
        }

        return has_more;
    }

    /// This function can be called multiple times in one tick of the event loop.
    /// See: https://github.com/nodejs/node/pull/38506
    /// In that case, we need to drain microtasks.
    fn call(this: *ThreadSafeFunction, task: ?*anyopaque, is_first: bool) bun.JSExecutionTerminated!void {
        const env = this.env;
        if (!is_first) {
            try this.event_loop.drainMicrotasks();
        }
        const globalObject = env.toJS();

        this.tracker.willDispatch(globalObject);
        defer this.tracker.didDispatch(globalObject);

        switch (this.callback) {
            .js => |strong| {
                const js: JSValue = strong.get() orelse .js_undefined;
                if (js.isEmptyOrUndefinedOrNull()) {
                    return;
                }

                _ = js.call(globalObject, .js_undefined, &.{}) catch |err|
                    globalObject.reportActiveExceptionAsUnhandled(err);
            },
            .c => |cb| {
                const js: JSValue = cb.js.get() orelse .js_undefined;

                const handle_scope = NapiHandleScope.open(env, false);
                defer if (handle_scope) |scope| scope.close(env);
                cb.napi_threadsafe_function_call_js(env, napi_value.create(env, js), this.ctx, task);
            },
        }
    }

    pub fn enqueue(this: *ThreadSafeFunction, ctx: ?*anyopaque, block: bool) napi_status {
        this.lock.lock();
        defer this.lock.unlock();
        if (block) {
            while (this.queue.isBlocked()) {
                this.blocking_condvar.wait(&this.lock);
            }
        } else {
            if (this.queue.isBlocked()) {
                // don't set the error on the env as this is run from another thread
                return @intFromEnum(NapiStatus.queue_full);
            }
        }

        if (this.isClosing()) {
            if (this.thread_count.load(.seq_cst) <= 0) {
                return @intFromEnum(NapiStatus.invalid_arg);
            }
            _ = this.release(.release, true);
            return @intFromEnum(NapiStatus.closing);
        }

        _ = this.queue.count.fetchAdd(1, .seq_cst);
        this.queue.data.writeItem(ctx) catch bun.outOfMemory();
        this.scheduleDispatch();
        return @intFromEnum(NapiStatus.ok);
    }

    fn scheduleDispatch(this: *ThreadSafeFunction) void {
        switch (this.dispatch_state.swap(.pending, .seq_cst)) {
            .idle => {
                this.event_loop.enqueueTaskConcurrent(jsc.ConcurrentTask.createFrom(this));
            },
            .running => {
                // it will check if it has more work to do
            },
            .pending => {
                // we've already scheduled it to run
            },
        }
    }

    pub fn deinit(this: *ThreadSafeFunction) void {
        this.unref();

        if (this.finalizer.fun) |fun| {
            Finalizer.napi_internal_enqueue_finalizer(this.env, fun, this.finalizer.data, this.ctx);
        }

        this.callback.deinit();
        this.queue.deinit();
        bun.destroy(this);
    }

    pub fn ref(this: *ThreadSafeFunction) void {
        this.poll_ref.refConcurrentlyFromEventLoop(this.event_loop);
    }

    pub fn unref(this: *ThreadSafeFunction) void {
        this.poll_ref.unrefConcurrentlyFromEventLoop(this.event_loop);
    }

    pub fn acquire(this: *ThreadSafeFunction) napi_status {
        this.lock.lock();
        defer this.lock.unlock();
        if (this.isClosing()) {
            return @intFromEnum(NapiStatus.closing);
        }
        _ = this.thread_count.fetchAdd(1, .seq_cst);
        return @intFromEnum(NapiStatus.ok);
    }

    pub fn release(this: *ThreadSafeFunction, mode: napi_threadsafe_function_release_mode, already_locked: bool) napi_status {
        if (!already_locked) this.lock.lock();
        defer if (!already_locked) this.lock.unlock();

        if (this.thread_count.load(.seq_cst) < 0) {
            return @intFromEnum(NapiStatus.invalid_arg);
        }

        const prev_remaining = this.thread_count.fetchSub(1, .seq_cst);

        if (mode == .abort or prev_remaining == 1) {
            if (!this.isClosing()) {
                if (mode == .abort) {
                    this.closing.store(.closing, .seq_cst);
                    this.aborted.store(true, .seq_cst);
                    if (this.queue.max_queue_size > 0) {
                        this.blocking_condvar.signal();
                    }
                }
                this.scheduleDispatch();
            }
        }

        return @intFromEnum(NapiStatus.ok);
    }
};

pub export fn napi_create_threadsafe_function(
    env_: napi_env,
    func_: napi_value,
    _: napi_value, // async_resource
    _: napi_value, // async_resource_name
    max_queue_size: usize,
    initial_thread_count: usize,
    thread_finalize_data: ?*anyopaque,
    thread_finalize_cb: napi_finalize,
    context: ?*anyopaque,
    call_js_cb: ?napi_threadsafe_function_call_js,
    result_: ?*napi_threadsafe_function,
) napi_status {
    log("napi_create_threadsafe_function", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    const result = result_ orelse {
        return env.invalidArg();
    };
    const func = func_.get();

    if (call_js_cb == null and (func.isEmptyOrUndefinedOrNull() or !func.isCallable())) {
        return env.setLastError(.function_expected);
    }

    const vm = env.toJS().bunVM();
    var function = ThreadSafeFunction.new(.{
        .event_loop = vm.eventLoop(),
        .env = env,
        .callback = if (call_js_cb) |c| .{
            .c = .{
                .napi_threadsafe_function_call_js = c,
                .js = if (func == .zero) .empty else jsc.Strong.Optional.create(func.withAsyncContextIfNeeded(env.toJS()), vm.global),
            },
        } else .{
            .js = if (func == .zero) .empty else jsc.Strong.Optional.create(func.withAsyncContextIfNeeded(env.toJS()), vm.global),
        },
        .ctx = context,
        .queue = ThreadSafeFunction.Queue.init(max_queue_size, bun.default_allocator),
        .thread_count = .{ .raw = @intCast(initial_thread_count) },
        .poll_ref = Async.KeepAlive.init(),
        .tracker = jsc.Debugger.AsyncTaskTracker.init(vm),
    });

    function.finalizer = .{ .env = env, .data = thread_finalize_data, .fun = thread_finalize_cb };
    // nodejs by default keeps the event loop alive until the thread-safe function is unref'd
    function.ref();
    function.tracker.didSchedule(vm.global);

    result.* = function;
    return env.ok();
}
pub export fn napi_get_threadsafe_function_context(func: napi_threadsafe_function, result: *?*anyopaque) napi_status {
    log("napi_get_threadsafe_function_context", .{});
    result.* = func.ctx;
    return @intFromEnum(NapiStatus.ok);
}
pub export fn napi_call_threadsafe_function(func: napi_threadsafe_function, data: ?*anyopaque, is_blocking: napi_threadsafe_function_call_mode) napi_status {
    log("napi_call_threadsafe_function", .{});
    return func.enqueue(data, is_blocking == napi_tsfn_blocking);
}
pub export fn napi_acquire_threadsafe_function(func: napi_threadsafe_function) napi_status {
    log("napi_acquire_threadsafe_function", .{});
    return func.acquire();
}
pub export fn napi_release_threadsafe_function(func: napi_threadsafe_function, mode: napi_threadsafe_function_release_mode) napi_status {
    log("napi_release_threadsafe_function", .{});
    return func.release(mode, false);
}
pub export fn napi_unref_threadsafe_function(env_: napi_env, func: napi_threadsafe_function) napi_status {
    log("napi_unref_threadsafe_function", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    bun.assert(func.event_loop.global == env.toJS());
    func.unref();
    return env.ok();
}
pub export fn napi_ref_threadsafe_function(env_: napi_env, func: napi_threadsafe_function) napi_status {
    log("napi_ref_threadsafe_function", .{});
    const env = env_ orelse {
        return envIsNull();
    };
    bun.assert(func.event_loop.global == env.toJS());
    func.ref();
    return env.ok();
}

const NAPI_AUTO_LENGTH = std.math.maxInt(usize);

/// v8:: C++ symbols defined in v8.cpp
///
/// Do not call these at runtime, as they do not contain type and callconv info. They are simply
/// used for DCE suppression and asserting that the symbols exist at link-time.
///
// TODO: write a script to generate this struct. ideally it wouldn't even need to be committed to source.
const V8API = if (!bun.Environment.isWindows) struct {
    pub extern fn _ZN2v87Isolate10GetCurrentEv() *anyopaque;
    pub extern fn _ZN2v87Isolate13TryGetCurrentEv() *anyopaque;
    pub extern fn _ZN2v87Isolate17GetCurrentContextEv() *anyopaque;
    pub extern fn _ZN4node25AddEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_() *anyopaque;
    pub extern fn _ZN4node28RemoveEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_() *anyopaque;
    pub extern fn _ZN2v86Number3NewEPNS_7IsolateEd() *anyopaque;
    pub extern fn _ZNK2v86Number5ValueEv() *anyopaque;
    pub extern fn _ZN2v86String11NewFromUtf8EPNS_7IsolateEPKcNS_13NewStringTypeEi() *anyopaque;
    pub extern fn _ZNK2v86String9WriteUtf8EPNS_7IsolateEPciPii() *anyopaque;
    pub extern fn _ZN2v812api_internal12ToLocalEmptyEv() *anyopaque;
    pub extern fn _ZNK2v86String6LengthEv() *anyopaque;
    pub extern fn _ZN2v88External3NewEPNS_7IsolateEPv() *anyopaque;
    pub extern fn _ZNK2v88External5ValueEv() *anyopaque;
    pub extern fn _ZN2v86Object3NewEPNS_7IsolateE() *anyopaque;
    pub extern fn _ZN2v86Object3SetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEES5_() *anyopaque;
    pub extern fn _ZN2v86Object3SetENS_5LocalINS_7ContextEEEjNS1_INS_5ValueEEE() *anyopaque;
    pub extern fn _ZN2v86Object16SetInternalFieldEiNS_5LocalINS_4DataEEE() *anyopaque;
    pub extern fn _ZN2v86Object20SlowGetInternalFieldEi() *anyopaque;
    pub extern fn _ZN2v86Object3GetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEE() *anyopaque;
    pub extern fn _ZN2v86Object3GetENS_5LocalINS_7ContextEEEj() *anyopaque;
    pub extern fn _ZN2v811HandleScope12CreateHandleEPNS_8internal7IsolateEm() *anyopaque;
    pub extern fn _ZN2v811HandleScopeC1EPNS_7IsolateE() *anyopaque;
    pub extern fn _ZN2v811HandleScopeD1Ev() *anyopaque;
    pub extern fn _ZN2v811HandleScopeD2Ev() *anyopaque;
    pub extern fn _ZN2v816FunctionTemplate11GetFunctionENS_5LocalINS_7ContextEEE() *anyopaque;
    pub extern fn _ZN2v816FunctionTemplate3NewEPNS_7IsolateEPFvRKNS_20FunctionCallbackInfoINS_5ValueEEEENS_5LocalIS4_EENSA_INS_9SignatureEEEiNS_19ConstructorBehaviorENS_14SideEffectTypeEPKNS_9CFunctionEttt() *anyopaque;
    pub extern fn _ZN2v814ObjectTemplate11NewInstanceENS_5LocalINS_7ContextEEE() *anyopaque;
    pub extern fn _ZN2v814ObjectTemplate21SetInternalFieldCountEi() *anyopaque;
    pub extern fn _ZNK2v814ObjectTemplate18InternalFieldCountEv() *anyopaque;
    pub extern fn _ZN2v814ObjectTemplate3NewEPNS_7IsolateENS_5LocalINS_16FunctionTemplateEEE() *anyopaque;
    pub extern fn _ZN2v824EscapableHandleScopeBase10EscapeSlotEPm() *anyopaque;
    pub extern fn _ZN2v824EscapableHandleScopeBaseC2EPNS_7IsolateE() *anyopaque;
    pub extern fn _ZN2v88internal35IsolateFromNeverReadOnlySpaceObjectEm() *anyopaque;
    pub extern fn _ZN2v85Array3NewEPNS_7IsolateEPNS_5LocalINS_5ValueEEEm() *anyopaque;
    pub extern fn _ZNK2v85Array6LengthEv() *anyopaque;
    pub extern fn _ZN2v85Array3NewEPNS_7IsolateEi() *anyopaque;
    pub extern fn _ZN2v85Array7IterateENS_5LocalINS_7ContextEEEPFNS0_14CallbackResultEjNS1_INS_5ValueEEEPvES7_() *anyopaque;
    pub extern fn _ZN2v85Array9CheckCastEPNS_5ValueE() *anyopaque;
    pub extern fn _ZN2v88Function7SetNameENS_5LocalINS_6StringEEE() *anyopaque;
    pub extern fn _ZNK2v85Value9IsBooleanEv() *anyopaque;
    pub extern fn _ZNK2v87Boolean5ValueEv() *anyopaque;
    pub extern fn _ZNK2v85Value10FullIsTrueEv() *anyopaque;
    pub extern fn _ZNK2v85Value11FullIsFalseEv() *anyopaque;
    pub extern fn _ZN2v820EscapableHandleScopeC1EPNS_7IsolateE() *anyopaque;
    pub extern fn _ZN2v820EscapableHandleScopeC2EPNS_7IsolateE() *anyopaque;
    pub extern fn _ZN2v820EscapableHandleScopeD1Ev() *anyopaque;
    pub extern fn _ZN2v820EscapableHandleScopeD2Ev() *anyopaque;
    pub extern fn _ZNK2v85Value8IsObjectEv() *anyopaque;
    pub extern fn _ZNK2v85Value8IsNumberEv() *anyopaque;
    pub extern fn _ZNK2v85Value8IsUint32Ev() *anyopaque;
    pub extern fn _ZNK2v85Value11Uint32ValueENS_5LocalINS_7ContextEEE() *anyopaque;
    pub extern fn _ZNK2v85Value11IsUndefinedEv() *anyopaque;
    pub extern fn _ZNK2v85Value6IsNullEv() *anyopaque;
    pub extern fn _ZNK2v85Value17IsNullOrUndefinedEv() *anyopaque;
    pub extern fn _ZNK2v85Value6IsTrueEv() *anyopaque;
    pub extern fn _ZNK2v85Value7IsFalseEv() *anyopaque;
    pub extern fn _ZNK2v85Value8IsStringEv() *anyopaque;
    pub extern fn _ZNK2v85Value12StrictEqualsENS_5LocalIS0_EE() *anyopaque;
    pub extern fn _ZN2v87Boolean3NewEPNS_7IsolateEb() *anyopaque;
    pub extern fn _ZN2v86Object16GetInternalFieldEi() *anyopaque;
    pub extern fn _ZN2v87Context10GetIsolateEv() *anyopaque;
    pub extern fn _ZN2v86String14NewFromOneByteEPNS_7IsolateEPKhNS_13NewStringTypeEi() *anyopaque;
    pub extern fn _ZNK2v86String10Utf8LengthEPNS_7IsolateE() *anyopaque;
    pub extern fn _ZNK2v86String10IsExternalEv() *anyopaque;
    pub extern fn _ZNK2v86String17IsExternalOneByteEv() *anyopaque;
    pub extern fn _ZNK2v86String17IsExternalTwoByteEv() *anyopaque;
    pub extern fn _ZNK2v86String9IsOneByteEv() *anyopaque;
    pub extern fn _ZNK2v86String19ContainsOnlyOneByteEv() *anyopaque;
    pub extern fn _ZN2v812api_internal18GlobalizeReferenceEPNS_8internal7IsolateEm() *anyopaque;
    pub extern fn _ZN2v812api_internal13DisposeGlobalEPm() *anyopaque;
    pub extern fn _ZN2v812api_internal23GetFunctionTemplateDataEPNS_7IsolateENS_5LocalINS_4DataEEE() *anyopaque;
    pub extern fn _ZNK2v88Function7GetNameEv() *anyopaque;
    pub extern fn _ZNK2v85Value10IsFunctionEv() *anyopaque;
    pub extern fn _ZN2v812api_internal17FromJustIsNothingEv() *anyopaque;
    pub extern fn uv_os_getpid() *anyopaque;
    pub extern fn uv_os_getppid() *anyopaque;
} else struct {
    // MSVC name mangling is different than it is on unix.
    // To make this easier to deal with, I have provided a script to generate the list of functions.
    //
    // dumpbin .\build\CMakeFiles\bun-debug.dir\src\bun.js\bindings\v8\*.cpp.obj /symbols | where-object { $_.Contains(' node::') -or $_.Contains(' v8::') } | foreach-object { (($_ -split "\|")[1] -split " ")[1] } | ForEach-Object { "extern fn @`"${_}`"() *anyopaque;" }
    //
    // Bug @paperclover if you get stuck here
    pub extern fn @"?TryGetCurrent@Isolate@v8@@SAPEAV12@XZ"() *anyopaque;
    pub extern fn @"?GetCurrent@Isolate@v8@@SAPEAV12@XZ"() *anyopaque;
    pub extern fn @"?GetCurrentContext@Isolate@v8@@QEAA?AV?$Local@VContext@v8@@@2@XZ"() *anyopaque;
    pub extern fn @"?AddEnvironmentCleanupHook@node@@YAXPEAVIsolate@v8@@P6AXPEAX@Z1@Z"() *anyopaque;
    pub extern fn @"?RemoveEnvironmentCleanupHook@node@@YAXPEAVIsolate@v8@@P6AXPEAX@Z1@Z"() *anyopaque;
    pub extern fn @"?New@Number@v8@@SA?AV?$Local@VNumber@v8@@@2@PEAVIsolate@2@N@Z"() *anyopaque;
    pub extern fn @"?Value@Number@v8@@QEBANXZ"() *anyopaque;
    pub extern fn @"?NewFromUtf8@String@v8@@SA?AV?$MaybeLocal@VString@v8@@@2@PEAVIsolate@2@PEBDW4NewStringType@2@H@Z"() *anyopaque;
    pub extern fn @"?WriteUtf8@String@v8@@QEBAHPEAVIsolate@2@PEADHPEAHH@Z"() *anyopaque;
    pub extern fn @"?ToLocalEmpty@api_internal@v8@@YAXXZ"() *anyopaque;
    pub extern fn @"?Length@String@v8@@QEBAHXZ"() *anyopaque;
    pub extern fn @"?New@External@v8@@SA?AV?$Local@VExternal@v8@@@2@PEAVIsolate@2@PEAX@Z"() *anyopaque;
    pub extern fn @"?Value@External@v8@@QEBAPEAXXZ"() *anyopaque;
    pub extern fn @"?New@Object@v8@@SA?AV?$Local@VObject@v8@@@2@PEAVIsolate@2@@Z"() *anyopaque;
    pub extern fn @"?Set@Object@v8@@QEAA?AV?$Maybe@_N@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@1@Z"() *anyopaque;
    pub extern fn @"?Set@Object@v8@@QEAA?AV?$Maybe@_N@2@V?$Local@VContext@v8@@@2@IV?$Local@VValue@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?SetInternalField@Object@v8@@QEAAXHV?$Local@VData@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?SlowGetInternalField@Object@v8@@AEAA?AV?$Local@VData@v8@@@2@H@Z"() *anyopaque;
    pub extern fn @"?Get@Object@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@I@Z"() *anyopaque;
    pub extern fn @"?Get@Object@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?CreateHandle@HandleScope@v8@@KAPEA_KPEAVIsolate@internal@2@_K@Z"() *anyopaque;
    pub extern fn @"??0HandleScope@v8@@QEAA@PEAVIsolate@1@@Z"() *anyopaque;
    pub extern fn @"??1HandleScope@v8@@QEAA@XZ"() *anyopaque;
    pub extern fn @"?GetFunction@FunctionTemplate@v8@@QEAA?AV?$MaybeLocal@VFunction@v8@@@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?New@FunctionTemplate@v8@@SA?AV?$Local@VFunctionTemplate@v8@@@2@PEAVIsolate@2@P6AXAEBV?$FunctionCallbackInfo@VValue@v8@@@2@@ZV?$Local@VValue@v8@@@2@V?$Local@VSignature@v8@@@2@HW4ConstructorBehavior@2@W4SideEffectType@2@PEBVCFunction@2@GGG@Z"() *anyopaque;
    pub extern fn @"?NewInstance@ObjectTemplate@v8@@QEAA?AV?$MaybeLocal@VObject@v8@@@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?SetInternalFieldCount@ObjectTemplate@v8@@QEAAXH@Z"() *anyopaque;
    pub extern fn @"?InternalFieldCount@ObjectTemplate@v8@@QEBAHXZ"() *anyopaque;
    pub extern fn @"?New@ObjectTemplate@v8@@SA?AV?$Local@VObjectTemplate@v8@@@2@PEAVIsolate@2@V?$Local@VFunctionTemplate@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?EscapeSlot@EscapableHandleScopeBase@v8@@IEAAPEA_KPEA_K@Z"() *anyopaque;
    pub extern fn @"??0EscapableHandleScopeBase@v8@@QEAA@PEAVIsolate@1@@Z"() *anyopaque;
    pub extern fn @"?IsolateFromNeverReadOnlySpaceObject@internal@v8@@YAPEAVIsolate@12@_K@Z"() *anyopaque;
    pub extern fn @"?New@Array@v8@@SA?AV?$Local@VArray@v8@@@2@PEAVIsolate@2@PEAV?$Local@VValue@v8@@@2@_K@Z"() *anyopaque;
    pub extern fn @"?Length@Array@v8@@QEBAIXZ"() *anyopaque;
    pub extern fn @"?New@Array@v8@@SA?AV?$Local@VArray@v8@@@2@PEAVIsolate@2@H@Z"() *anyopaque;
    pub extern fn @"?New@Array@v8@@SA?AV?$MaybeLocal@VArray@v8@@@2@V?$Local@VContext@v8@@@2@_KV?$function@$$A6A?AV?$MaybeLocal@VValue@v8@@@v8@@XZ@std@@@Z"() *anyopaque;
    pub extern fn @"?Iterate@Array@v8@@QEAA?AV?$Maybe@X@2@V?$Local@VContext@v8@@@2@P6A?AW4CallbackResult@12@IV?$Local@VValue@v8@@@2@PEAX@Z2@Z"() *anyopaque;
    pub extern fn @"?CheckCast@Array@v8@@CAXPEAVValue@2@@Z"() *anyopaque;
    pub extern fn @"?SetName@Function@v8@@QEAAXV?$Local@VString@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?IsBoolean@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?Value@Boolean@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?FullIsTrue@Value@v8@@AEBA_NXZ"() *anyopaque;
    pub extern fn @"?FullIsFalse@Value@v8@@AEBA_NXZ"() *anyopaque;
    pub extern fn @"??1EscapableHandleScope@v8@@QEAA@XZ"() *anyopaque;
    pub extern fn @"??0EscapableHandleScope@v8@@QEAA@PEAVIsolate@1@@Z"() *anyopaque;
    pub extern fn @"?IsObject@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsNumber@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsUint32@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?Uint32Value@Value@v8@@QEBA?AV?$Maybe@I@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?IsUndefined@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsNull@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsNullOrUndefined@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsTrue@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsFalse@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsString@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?StrictEquals@Value@v8@@QEBA_NV?$Local@VValue@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?New@Boolean@v8@@SA?AV?$Local@VBoolean@v8@@@2@PEAVIsolate@2@_N@Z"() *anyopaque;
    pub extern fn @"?GetInternalField@Object@v8@@QEAA?AV?$Local@VData@v8@@@2@H@Z"() *anyopaque;
    pub extern fn @"?GetIsolate@Context@v8@@QEAAPEAVIsolate@2@XZ"() *anyopaque;
    pub extern fn @"?NewFromOneByte@String@v8@@SA?AV?$MaybeLocal@VString@v8@@@2@PEAVIsolate@2@PEBEW4NewStringType@2@H@Z"() *anyopaque;
    pub extern fn @"?IsExternal@String@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsExternalOneByte@String@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsExternalTwoByte@String@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?IsOneByte@String@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?Utf8Length@String@v8@@QEBAHPEAVIsolate@2@@Z"() *anyopaque;
    pub extern fn @"?ContainsOnlyOneByte@String@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?GlobalizeReference@api_internal@v8@@YAPEA_KPEAVIsolate@internal@2@_K@Z"() *anyopaque;
    pub extern fn @"?DisposeGlobal@api_internal@v8@@YAXPEA_K@Z"() *anyopaque;
    pub extern fn @"?GetFunctionTemplateData@api_internal@v8@@YA?AV?$Local@VValue@v8@@@2@PEAVIsolate@2@V?$Local@VData@v8@@@2@@Z"() *anyopaque;
    pub extern fn @"?GetName@Function@v8@@QEBA?AV?$Local@VValue@v8@@@2@XZ"() *anyopaque;
    pub extern fn @"?IsFunction@Value@v8@@QEBA_NXZ"() *anyopaque;
    pub extern fn @"?FromJustIsNothing@api_internal@v8@@YAXXZ"() *anyopaque;
};

/// V8 API functions whose mangled name differs between Linux and macOS
const posix_platform_specific_v8_apis = switch (bun.Environment.os) {
    .mac => struct {
        pub extern fn _ZN2v85Array3NewENS_5LocalINS_7ContextEEEmNSt3__18functionIFNS_10MaybeLocalINS_5ValueEEEvEEE() *anyopaque;
    },
    .linux => struct {
        pub extern fn _ZN2v85Array3NewENS_5LocalINS_7ContextEEEmSt8functionIFNS_10MaybeLocalINS_5ValueEEEvEE() *anyopaque;
    },
    .windows => struct {},
    else => unreachable,
};

// To update this list, use find + multi-cursor in your editor.
// - pub extern fn napi_
// - pub export fn napi_
const napi_functions_to_export = .{
    napi_acquire_threadsafe_function,
    napi_add_async_cleanup_hook,
    napi_add_env_cleanup_hook,
    napi_add_finalizer,
    napi_adjust_external_memory,
    napi_async_destroy,
    napi_async_init,
    napi_call_function,
    napi_call_threadsafe_function,
    napi_cancel_async_work,
    napi_check_object_type_tag,
    napi_close_callback_scope,
    napi_close_escapable_handle_scope,
    napi_close_handle_scope,
    napi_coerce_to_bool,
    napi_coerce_to_number,
    napi_coerce_to_object,
    napi_create_array,
    napi_create_array_with_length,
    napi_create_arraybuffer,
    napi_create_async_work,
    napi_create_bigint_int64,
    napi_create_bigint_uint64,
    napi_create_bigint_words,
    napi_create_buffer,
    napi_create_buffer_copy,
    napi_create_dataview,
    napi_create_date,
    napi_create_double,
    napi_create_error,
    napi_create_external,
    napi_create_external_arraybuffer,
    napi_create_external_buffer,
    napi_create_int32,
    napi_create_int64,
    napi_create_object,
    napi_create_promise,
    napi_create_range_error,
    napi_create_reference,
    napi_create_string_latin1,
    napi_create_string_utf16,
    napi_create_string_utf8,
    napi_create_symbol,
    napi_create_threadsafe_function,
    napi_create_type_error,
    napi_create_typedarray,
    napi_create_uint32,
    napi_define_class,
    napi_define_properties,
    napi_delete_async_work,
    napi_delete_element,
    napi_delete_reference,
    napi_detach_arraybuffer,
    napi_escape_handle,
    napi_fatal_error,
    napi_fatal_exception,
    napi_get_all_property_names,
    napi_get_and_clear_last_exception,
    napi_get_array_length,
    napi_get_arraybuffer_info,
    napi_get_boolean,
    napi_get_buffer_info,
    napi_get_cb_info,
    napi_get_dataview_info,
    napi_get_date_value,
    napi_get_element,
    napi_get_global,
    napi_get_instance_data,
    napi_get_last_error_info,
    napi_get_new_target,
    napi_get_node_version,
    napi_get_null,
    napi_get_prototype,
    napi_get_reference_value,
    napi_get_threadsafe_function_context,
    napi_get_typedarray_info,
    napi_get_undefined,
    napi_get_uv_event_loop,
    napi_get_value_bigint_int64,
    napi_get_value_bigint_uint64,
    napi_get_value_bigint_words,
    napi_get_value_bool,
    napi_get_value_double,
    napi_get_value_external,
    napi_get_value_int32,
    napi_get_value_int64,
    napi_get_value_string_latin1,
    napi_get_value_string_utf16,
    napi_get_value_string_utf8,
    napi_get_value_uint32,
    napi_get_version,
    napi_has_element,
    napi_instanceof,
    napi_is_array,
    napi_is_arraybuffer,
    napi_is_buffer,
    napi_is_dataview,
    napi_is_date,
    napi_is_detached_arraybuffer,
    napi_is_error,
    napi_is_exception_pending,
    napi_is_promise,
    napi_is_typedarray,
    napi_make_callback,
    napi_new_instance,
    napi_open_callback_scope,
    napi_open_escapable_handle_scope,
    napi_open_handle_scope,
    napi_queue_async_work,
    napi_ref_threadsafe_function,
    napi_reference_ref,
    napi_reference_unref,
    napi_reject_deferred,
    napi_release_threadsafe_function,
    napi_remove_async_cleanup_hook,
    napi_remove_env_cleanup_hook,
    napi_remove_wrap,
    napi_resolve_deferred,
    napi_run_script,
    napi_set_element,
    napi_set_instance_data,
    napi_strict_equals,
    napi_throw,
    napi_throw_error,
    napi_throw_range_error,
    napi_throw_type_error,
    napi_type_tag_object,
    napi_typeof,
    napi_unref_threadsafe_function,
    napi_unwrap,
    napi_wrap,

    // -- node-api
    node_api_create_syntax_error,
    node_api_symbol_for,
    node_api_throw_syntax_error,
    node_api_create_external_string_latin1,
    node_api_create_external_string_utf16,
};

const uv_functions_to_export = if (bun.Environment.isPosix) struct {
    pub extern "c" fn uv_accept() void;
    pub extern "c" fn uv_async_init() void;
    pub extern "c" fn uv_async_send() void;
    pub extern "c" fn uv_available_parallelism() void;
    pub extern "c" fn uv_backend_fd() void;
    pub extern "c" fn uv_backend_timeout() void;
    pub extern "c" fn uv_barrier_destroy() void;
    pub extern "c" fn uv_barrier_init() void;
    pub extern "c" fn uv_barrier_wait() void;
    pub extern "c" fn uv_buf_init() void;
    pub extern "c" fn uv_cancel() void;
    pub extern "c" fn uv_chdir() void;
    pub extern "c" fn uv_check_init() void;
    pub extern "c" fn uv_check_start() void;
    pub extern "c" fn uv_check_stop() void;
    pub extern "c" fn uv_clock_gettime() void;
    pub extern "c" fn uv_close() void;
    pub extern "c" fn uv_cond_broadcast() void;
    pub extern "c" fn uv_cond_destroy() void;
    pub extern "c" fn uv_cond_init() void;
    pub extern "c" fn uv_cond_signal() void;
    pub extern "c" fn uv_cond_timedwait() void;
    pub extern "c" fn uv_cond_wait() void;
    pub extern "c" fn uv_cpu_info() void;
    pub extern "c" fn uv_cpumask_size() void;
    pub extern "c" fn uv_cwd() void;
    pub extern "c" fn uv_default_loop() void;
    pub extern "c" fn uv_disable_stdio_inheritance() void;
    pub extern "c" fn uv_dlclose() void;
    pub extern "c" fn uv_dlerror() void;
    pub extern "c" fn uv_dlopen() void;
    pub extern "c" fn uv_dlsym() void;
    pub extern "c" fn uv_err_name() void;
    pub extern "c" fn uv_err_name_r() void;
    pub extern "c" fn uv_exepath() void;
    pub extern "c" fn uv_fileno() void;
    pub extern "c" fn uv_free_cpu_info() void;
    pub extern "c" fn uv_free_interface_addresses() void;
    pub extern "c" fn uv_freeaddrinfo() void;
    pub extern "c" fn uv_fs_access() void;
    pub extern "c" fn uv_fs_chmod() void;
    pub extern "c" fn uv_fs_chown() void;
    pub extern "c" fn uv_fs_close() void;
    pub extern "c" fn uv_fs_closedir() void;
    pub extern "c" fn uv_fs_copyfile() void;
    pub extern "c" fn uv_fs_event_getpath() void;
    pub extern "c" fn uv_fs_event_init() void;
    pub extern "c" fn uv_fs_event_start() void;
    pub extern "c" fn uv_fs_event_stop() void;
    pub extern "c" fn uv_fs_fchmod() void;
    pub extern "c" fn uv_fs_fchown() void;
    pub extern "c" fn uv_fs_fdatasync() void;
    pub extern "c" fn uv_fs_fstat() void;
    pub extern "c" fn uv_fs_fsync() void;
    pub extern "c" fn uv_fs_ftruncate() void;
    pub extern "c" fn uv_fs_futime() void;
    pub extern "c" fn uv_fs_get_path() void;
    pub extern "c" fn uv_fs_get_ptr() void;
    pub extern "c" fn uv_fs_get_result() void;
    pub extern "c" fn uv_fs_get_statbuf() void;
    pub extern "c" fn uv_fs_get_system_error() void;
    pub extern "c" fn uv_fs_get_type() void;
    pub extern "c" fn uv_fs_lchown() void;
    pub extern "c" fn uv_fs_link() void;
    pub extern "c" fn uv_fs_lstat() void;
    pub extern "c" fn uv_fs_lutime() void;
    pub extern "c" fn uv_fs_mkdir() void;
    pub extern "c" fn uv_fs_mkdtemp() void;
    pub extern "c" fn uv_fs_mkstemp() void;
    pub extern "c" fn uv_fs_open() void;
    pub extern "c" fn uv_fs_opendir() void;
    pub extern "c" fn uv_fs_poll_getpath() void;
    pub extern "c" fn uv_fs_poll_init() void;
    pub extern "c" fn uv_fs_poll_start() void;
    pub extern "c" fn uv_fs_poll_stop() void;
    pub extern "c" fn uv_fs_read() void;
    pub extern "c" fn uv_fs_readdir() void;
    pub extern "c" fn uv_fs_readlink() void;
    pub extern "c" fn uv_fs_realpath() void;
    pub extern "c" fn uv_fs_rename() void;
    pub extern "c" fn uv_fs_req_cleanup() void;
    pub extern "c" fn uv_fs_rmdir() void;
    pub extern "c" fn uv_fs_scandir() void;
    pub extern "c" fn uv_fs_scandir_next() void;
    pub extern "c" fn uv_fs_sendfile() void;
    pub extern "c" fn uv_fs_stat() void;
    pub extern "c" fn uv_fs_statfs() void;
    pub extern "c" fn uv_fs_symlink() void;
    pub extern "c" fn uv_fs_unlink() void;
    pub extern "c" fn uv_fs_utime() void;
    pub extern "c" fn uv_fs_write() void;
    pub extern "c" fn uv_get_available_memory() void;
    pub extern "c" fn uv_get_constrained_memory() void;
    pub extern "c" fn uv_get_free_memory() void;
    pub extern "c" fn uv_get_osfhandle() void;
    pub extern "c" fn uv_get_process_title() void;
    pub extern "c" fn uv_get_total_memory() void;
    pub extern "c" fn uv_getaddrinfo() void;
    pub extern "c" fn uv_getnameinfo() void;
    pub extern "c" fn uv_getrusage() void;
    pub extern "c" fn uv_getrusage_thread() void;
    pub extern "c" fn uv_gettimeofday() void;
    pub extern "c" fn uv_guess_handle() void;
    pub extern "c" fn uv_handle_get_data() void;
    pub extern "c" fn uv_handle_get_loop() void;
    pub extern "c" fn uv_handle_get_type() void;
    pub extern "c" fn uv_handle_set_data() void;
    pub extern "c" fn uv_handle_size() void;
    pub extern "c" fn uv_handle_type_name() void;
    pub extern "c" fn uv_has_ref() void;
    pub extern "c" fn uv_hrtime() void;
    pub extern "c" fn uv_idle_init() void;
    pub extern "c" fn uv_idle_start() void;
    pub extern "c" fn uv_idle_stop() void;
    pub extern "c" fn uv_if_indextoiid() void;
    pub extern "c" fn uv_if_indextoname() void;
    pub extern "c" fn uv_inet_ntop() void;
    pub extern "c" fn uv_inet_pton() void;
    pub extern "c" fn uv_interface_addresses() void;
    pub extern "c" fn uv_ip_name() void;
    pub extern "c" fn uv_ip4_addr() void;
    pub extern "c" fn uv_ip4_name() void;
    pub extern "c" fn uv_ip6_addr() void;
    pub extern "c" fn uv_ip6_name() void;
    pub extern "c" fn uv_is_active() void;
    pub extern "c" fn uv_is_closing() void;
    pub extern "c" fn uv_is_readable() void;
    pub extern "c" fn uv_is_writable() void;
    pub extern "c" fn uv_key_create() void;
    pub extern "c" fn uv_key_delete() void;
    pub extern "c" fn uv_key_get() void;
    pub extern "c" fn uv_key_set() void;
    pub extern "c" fn uv_kill() void;
    pub extern "c" fn uv_library_shutdown() void;
    pub extern "c" fn uv_listen() void;
    pub extern "c" fn uv_loadavg() void;
    pub extern "c" fn uv_loop_alive() void;
    pub extern "c" fn uv_loop_close() void;
    pub extern "c" fn uv_loop_configure() void;
    pub extern "c" fn uv_loop_delete() void;
    pub extern "c" fn uv_loop_fork() void;
    pub extern "c" fn uv_loop_get_data() void;
    pub extern "c" fn uv_loop_init() void;
    pub extern "c" fn uv_loop_new() void;
    pub extern "c" fn uv_loop_set_data() void;
    pub extern "c" fn uv_loop_size() void;
    pub extern "c" fn uv_metrics_idle_time() void;
    pub extern "c" fn uv_metrics_info() void;
    pub extern "c" fn uv_mutex_destroy() void;
    pub extern "c" fn uv_mutex_init() void;
    pub extern "c" fn uv_mutex_init_recursive() void;
    pub extern "c" fn uv_mutex_lock() void;
    pub extern "c" fn uv_mutex_trylock() void;
    pub extern "c" fn uv_mutex_unlock() void;
    pub extern "c" fn uv_now() void;
    pub extern "c" fn uv_once() void;
    pub extern "c" fn uv_open_osfhandle() void;
    pub extern "c" fn uv_os_environ() void;
    pub extern "c" fn uv_os_free_environ() void;
    pub extern "c" fn uv_os_free_group() void;
    pub extern "c" fn uv_os_free_passwd() void;
    pub extern "c" fn uv_os_get_group() void;
    pub extern "c" fn uv_os_get_passwd() void;
    pub extern "c" fn uv_os_get_passwd2() void;
    pub extern "c" fn uv_os_getenv() void;
    pub extern "c" fn uv_os_gethostname() void;
    pub extern "c" fn uv_os_getpid() void;
    pub extern "c" fn uv_os_getppid() void;
    pub extern "c" fn uv_os_getpriority() void;
    pub extern "c" fn uv_os_homedir() void;
    pub extern "c" fn uv_os_setenv() void;
    pub extern "c" fn uv_os_setpriority() void;
    pub extern "c" fn uv_os_tmpdir() void;
    pub extern "c" fn uv_os_uname() void;
    pub extern "c" fn uv_os_unsetenv() void;
    pub extern "c" fn uv_pipe() void;
    pub extern "c" fn uv_pipe_bind() void;
    pub extern "c" fn uv_pipe_bind2() void;
    pub extern "c" fn uv_pipe_chmod() void;
    pub extern "c" fn uv_pipe_connect() void;
    pub extern "c" fn uv_pipe_connect2() void;
    pub extern "c" fn uv_pipe_getpeername() void;
    pub extern "c" fn uv_pipe_getsockname() void;
    pub extern "c" fn uv_pipe_init() void;
    pub extern "c" fn uv_pipe_open() void;
    pub extern "c" fn uv_pipe_pending_count() void;
    pub extern "c" fn uv_pipe_pending_instances() void;
    pub extern "c" fn uv_pipe_pending_type() void;
    pub extern "c" fn uv_poll_init() void;
    pub extern "c" fn uv_poll_init_socket() void;
    pub extern "c" fn uv_poll_start() void;
    pub extern "c" fn uv_poll_stop() void;
    pub extern "c" fn uv_prepare_init() void;
    pub extern "c" fn uv_prepare_start() void;
    pub extern "c" fn uv_prepare_stop() void;
    pub extern "c" fn uv_print_active_handles() void;
    pub extern "c" fn uv_print_all_handles() void;
    pub extern "c" fn uv_process_get_pid() void;
    pub extern "c" fn uv_process_kill() void;
    pub extern "c" fn uv_queue_work() void;
    pub extern "c" fn uv_random() void;
    pub extern "c" fn uv_read_start() void;
    pub extern "c" fn uv_read_stop() void;
    pub extern "c" fn uv_recv_buffer_size() void;
    pub extern "c" fn uv_ref() void;
    pub extern "c" fn uv_replace_allocator() void;
    pub extern "c" fn uv_req_get_data() void;
    pub extern "c" fn uv_req_get_type() void;
    pub extern "c" fn uv_req_set_data() void;
    pub extern "c" fn uv_req_size() void;
    pub extern "c" fn uv_req_type_name() void;
    pub extern "c" fn uv_resident_set_memory() void;
    pub extern "c" fn uv_run() void;
    pub extern "c" fn uv_rwlock_destroy() void;
    pub extern "c" fn uv_rwlock_init() void;
    pub extern "c" fn uv_rwlock_rdlock() void;
    pub extern "c" fn uv_rwlock_rdunlock() void;
    pub extern "c" fn uv_rwlock_tryrdlock() void;
    pub extern "c" fn uv_rwlock_trywrlock() void;
    pub extern "c" fn uv_rwlock_wrlock() void;
    pub extern "c" fn uv_rwlock_wrunlock() void;
    pub extern "c" fn uv_sem_destroy() void;
    pub extern "c" fn uv_sem_init() void;
    pub extern "c" fn uv_sem_post() void;
    pub extern "c" fn uv_sem_trywait() void;
    pub extern "c" fn uv_sem_wait() void;
    pub extern "c" fn uv_send_buffer_size() void;
    pub extern "c" fn uv_set_process_title() void;
    pub extern "c" fn uv_setup_args() void;
    pub extern "c" fn uv_shutdown() void;
    pub extern "c" fn uv_signal_init() void;
    pub extern "c" fn uv_signal_start() void;
    pub extern "c" fn uv_signal_start_oneshot() void;
    pub extern "c" fn uv_signal_stop() void;
    pub extern "c" fn uv_sleep() void;
    pub extern "c" fn uv_socketpair() void;
    pub extern "c" fn uv_spawn() void;
    pub extern "c" fn uv_stop() void;
    pub extern "c" fn uv_stream_get_write_queue_size() void;
    pub extern "c" fn uv_stream_set_blocking() void;
    pub extern "c" fn uv_strerror() void;
    pub extern "c" fn uv_strerror_r() void;
    pub extern "c" fn uv_tcp_bind() void;
    pub extern "c" fn uv_tcp_close_reset() void;
    pub extern "c" fn uv_tcp_connect() void;
    pub extern "c" fn uv_tcp_getpeername() void;
    pub extern "c" fn uv_tcp_getsockname() void;
    pub extern "c" fn uv_tcp_init() void;
    pub extern "c" fn uv_tcp_init_ex() void;
    pub extern "c" fn uv_tcp_keepalive() void;
    pub extern "c" fn uv_tcp_nodelay() void;
    pub extern "c" fn uv_tcp_open() void;
    pub extern "c" fn uv_tcp_simultaneous_accepts() void;
    pub extern "c" fn uv_thread_create() void;
    pub extern "c" fn uv_thread_create_ex() void;
    pub extern "c" fn uv_thread_detach() void;
    pub extern "c" fn uv_thread_equal() void;
    pub extern "c" fn uv_thread_getaffinity() void;
    pub extern "c" fn uv_thread_getcpu() void;
    pub extern "c" fn uv_thread_getname() void;
    pub extern "c" fn uv_thread_getpriority() void;
    pub extern "c" fn uv_thread_join() void;
    pub extern "c" fn uv_thread_self() void;
    pub extern "c" fn uv_thread_setaffinity() void;
    pub extern "c" fn uv_thread_setname() void;
    pub extern "c" fn uv_thread_setpriority() void;
    pub extern "c" fn uv_timer_again() void;
    pub extern "c" fn uv_timer_get_due_in() void;
    pub extern "c" fn uv_timer_get_repeat() void;
    pub extern "c" fn uv_timer_init() void;
    pub extern "c" fn uv_timer_set_repeat() void;
    pub extern "c" fn uv_timer_start() void;
    pub extern "c" fn uv_timer_stop() void;
    pub extern "c" fn uv_translate_sys_error() void;
    pub extern "c" fn uv_try_write() void;
    pub extern "c" fn uv_try_write2() void;
    pub extern "c" fn uv_tty_get_vterm_state() void;
    pub extern "c" fn uv_tty_get_winsize() void;
    pub extern "c" fn uv_tty_init() void;
    pub extern "c" fn uv_tty_reset_mode() void;
    pub extern "c" fn uv_tty_set_mode() void;
    pub extern "c" fn uv_tty_set_vterm_state() void;
    pub extern "c" fn uv_udp_bind() void;
    pub extern "c" fn uv_udp_connect() void;
    pub extern "c" fn uv_udp_get_send_queue_count() void;
    pub extern "c" fn uv_udp_get_send_queue_size() void;
    pub extern "c" fn uv_udp_getpeername() void;
    pub extern "c" fn uv_udp_getsockname() void;
    pub extern "c" fn uv_udp_init() void;
    pub extern "c" fn uv_udp_init_ex() void;
    pub extern "c" fn uv_udp_open() void;
    pub extern "c" fn uv_udp_recv_start() void;
    pub extern "c" fn uv_udp_recv_stop() void;
    pub extern "c" fn uv_udp_send() void;
    pub extern "c" fn uv_udp_set_broadcast() void;
    pub extern "c" fn uv_udp_set_membership() void;
    pub extern "c" fn uv_udp_set_multicast_interface() void;
    pub extern "c" fn uv_udp_set_multicast_loop() void;
    pub extern "c" fn uv_udp_set_multicast_ttl() void;
    pub extern "c" fn uv_udp_set_source_membership() void;
    pub extern "c" fn uv_udp_set_ttl() void;
    pub extern "c" fn uv_udp_try_send() void;
    pub extern "c" fn uv_udp_try_send2() void;
    pub extern "c" fn uv_udp_using_recvmmsg() void;
    pub extern "c" fn uv_unref() void;
    pub extern "c" fn uv_update_time() void;
    pub extern "c" fn uv_uptime() void;
    pub extern "c" fn uv_utf16_length_as_wtf8() void;
    pub extern "c" fn uv_utf16_to_wtf8() void;
    pub extern "c" fn uv_version() void;
    pub extern "c" fn uv_version_string() void;
    pub extern "c" fn uv_walk() void;
    pub extern "c" fn uv_write() void;
    pub extern "c" fn uv_write2() void;
    pub extern "c" fn uv_wtf8_length_as_utf16() void;
    pub extern "c" fn uv_wtf8_to_utf16() void;
} else struct {};

pub fn fixDeadCodeElimination() void {
    jsc.markBinding(@src());

    inline for (napi_functions_to_export) |fn_name| {
        std.mem.doNotOptimizeAway(&fn_name);
    }

    inline for (comptime std.meta.declarations(uv_functions_to_export)) |decl| {
        std.mem.doNotOptimizeAway(&@field(uv_functions_to_export, decl.name));
    }

    inline for (comptime std.meta.declarations(V8API)) |decl| {
        std.mem.doNotOptimizeAway(&@field(V8API, decl.name));
    }

    inline for (comptime std.meta.declarations(posix_platform_specific_v8_apis)) |decl| {
        std.mem.doNotOptimizeAway(&@field(posix_platform_specific_v8_apis, decl.name));
    }

    std.mem.doNotOptimizeAway(&@import("../bun.js/node/buffer.zig").BufferVectorized.fill);
}

pub const NapiFinalizerTask = struct {
    finalizer: Finalizer,

    const AnyTask = jsc.AnyTask.New(@This(), runOnJSThread);

    pub fn init(finalizer: Finalizer) *NapiFinalizerTask {
        const finalizer_task = bun.default_allocator.create(NapiFinalizerTask) catch bun.outOfMemory();
        finalizer_task.* = .{
            .finalizer = finalizer,
        };
        return finalizer_task;
    }

    pub fn schedule(this: *NapiFinalizerTask) void {
        const globalThis = this.finalizer.env.?.toJS();

        const vm, const thread_kind = globalThis.tryBunVM();

        if (thread_kind != .main) {
            // TODO(@heimskr): do we need to handle the case where the vm is shutting down?
            vm.eventLoop().enqueueTaskConcurrent(jsc.ConcurrentTask.create(jsc.Task.init(this)));
            return;
        }

        if (vm.isShuttingDown()) {
            // Immediate tasks won't run, so we run this as a cleanup hook instead
            vm.rareData().pushCleanupHook(vm.global, this, runAsCleanupHook);
        } else {
            globalThis.bunVM().event_loop.enqueueTask(jsc.Task.init(this));
        }
    }

    pub fn deinit(this: *NapiFinalizerTask) void {
        bun.default_allocator.destroy(this);
    }

    pub fn runOnJSThread(this: *NapiFinalizerTask) void {
        this.finalizer.run();
        this.deinit();
    }

    fn runAsCleanupHook(opaque_this: ?*anyopaque) callconv(.c) void {
        const this: *NapiFinalizerTask = @alignCast(@ptrCast(opaque_this.?));
        this.runOnJSThread();
    }
};

const std = @import("std");

const WorkPool = @import("../work_pool.zig").WorkPool;
const WorkPoolTask = @import("../work_pool.zig").Task;

const bun = @import("bun");
const Async = bun.Async;

const jsc = bun.jsc;
const JSValue = jsc.JSValue;
